2025 PHP7/8 实战入门:15 天精通现代 Web 开发——第 12 课:会话(Session)与 Cookie

第 12 课:会话(Session)与 Cookie

一、学习目标

  1. 理解 Session 与 Cookie 的工作原理、核心差异及适用场景
  2. 掌握 PHP Session 的安全配置、基础操作及分布式部署方案
  3. 熟练运用 Cookie 实现 “记住我” 功能与跨域状态保持,理解SameSite等安全属性
  4. 解决 Session 并发锁、Cookie 拦截、分布式 Session 同步等实际开发问题
  5. 能够基于 Session 与 Cookie 设计安全的用户认证系统(含自动登录、跨域场景)

二、核心知识点(完整整合)

(一)Session 与 Cookie 基础
  1. 核心概念与本质差异

    • Cookie:存储在客户端(浏览器)的小型文本文件,由服务器通过Set-Cookie响应头设置,每次 HTTP 请求自动携带(上限约 4KB,适合存储少量非敏感数据)。
    • Session:存储在服务器端(文件 / Redis / 数据库)的键值对数据,通过客户端 Cookie 中的PHPSESSID(会话 ID)关联用户(无大小限制,适合存储敏感数据如用户登录状态)。

    核心差异对比表:

    特性CookieSession
    存储位置客户端(浏览器)服务器端
    数据大小限制通常≤4KB无限制(受服务器内存影响)
    安全性较低(可被客户端篡改)较高(仅存会话 ID 在客户端)
    生命周期可设置长期有效(如 7 天)默认会话结束(浏览器关闭)
    传输方式每次请求自动携带仅通过PHPSESSID关联
    适用场景记住登录状态、用户偏好用户登录信息、临时业务数据
  2. Cookie 安全属性详解

    • HttpOnly:禁止 JavaScript 访问 Cookie,防御 XSS 攻击(强制开启)。
    • Secure:仅通过 HTTPS 协议传输 Cookie,防止中间人窃取(生产环境强制开启)。
    • SameSite:限制 Cookie 跨站发送,防御 CSRF 攻击,取值:
      • Strict:仅同站请求(同一域名)可携带,完全禁止跨站(如从百度跳转至网站不携带)。
      • Lax:默认值,允许部分跨站请求(如 GET 表单、链接跳转)携带,阻断 POST 跨站请求。
      • None:允许跨站请求携带(如前后端分离场景),必须同时设置Secure(仅 HTTPS)
    • Expires/Max-Age:设置 Cookie 有效期(Expires为具体时间,Max-Age为秒数,0 表示会话结束失效)。

    示例(生产环境 Cookie 完整配置):

    <?php
    $cookie_config = [
        'name' => 'user_auth',          // Cookie名称
        'value' => 'encrypted_data',    // 加密后的Cookie值
        'expires' => time() + 7 * 24 * 3600, // 7天有效期
        'path' => '/',                  // 全站有效(仅该路径下的请求携带)
        'domain' => '.example.com',     // 跨子域共享(如a.example.com与b.example.com)
        'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on', // 仅HTTPS传输
        'httponly' => true,             // 禁止JS访问,防XSS
        'samesite' => 'Lax'             // 防CSRF,跨站场景设为None(需配合Secure)
    ];
    
    // PHP7.3+支持数组形式传参,低版本需手动拼接SameSite头
    if (PHP_VERSION_ID >= 70300) {
        setcookie(
            $cookie_config['name'],
            $cookie_config['value'],
            $cookie_config
        );
    } else {
        setcookie(
            $cookie_config['name'],
            $cookie_config['value'],
            $cookie_config['expires'],
            $cookie_config['path'] . '; SameSite=' . $cookie_config['samesite'],
            $cookie_config['domain'],
            $cookie_config['secure'],
            $cookie_config['httponly']
        );
    }
    ?>
    
  3. 工作流程拆解

    • Cookie 流程:服务器→发送Set-Cookie响应头→浏览器保存 Cookie→后续请求自动携带Cookie请求头→服务器通过$_COOKIE读取。
    • Session 流程:客户端首次请求→服务器创建 Session(生成PHPSESSID)→通过 Cookie 发送PHPSESSID→客户端后续请求携带PHPSESSID→服务器通过PHPSESSID匹配 Session 数据。
(二)PHP Session 实战
  1. Session 基础配置与操作

    • 核心步骤session_start()(启动会话,必须在输出前调用)→操作$_SESSION数组→session_destroy()(可选,销毁会话)。
    • 安全配置:通过session_start()参数或php.ini优化安全性,避免会话劫持、固定攻击。

    示例(Session 安全初始化与基础操作):

    <?php
    // 1. 启动会话(安全配置,替代php.ini全局设置)
    $session_config = [
        'name' => 'APP_SESSID',         // 自定义会话ID Cookie名称(避免默认PHPSESSID)
        'cookie_lifetime' => 0,         // Cookie有效期(0表示会话结束失效)
        'cookie_path' => '/',           // 会话Cookie有效路径
        'cookie_domain' => '',          // 默认为当前域名,跨子域设为'.example.com'
        'cookie_secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on', // 仅HTTPS
        'cookie_httponly' => true,      // 禁止JS访问,防XSS
        'cookie_samesite' => 'Lax',     // 防CSRF
        'use_strict_mode' => true,      // 拒绝无效会话ID(防会话固定攻击)
        'gc_probability' => 1,          // 垃圾回收概率(1/100)
        'gc_divisor' => 100,
        'gc_maxlifetime' => 1440,       // 会话数据有效期(1440秒=24分钟)
        'read_and_close' => false       // 读取后是否立即关闭(高并发场景可设为true)
    ];
    session_start($session_config);
    
    // 2. Session基础操作(存储、读取、删除)
    // 存储用户登录状态
    $_SESSION['user'] = [
        'id' => 1,
        'username' => 'admin',
        'role' => 'admin',
        'login_time' => date('Y-m-d H:i:s'),
        'login_ip' => $_SERVER['REMOTE_ADDR']
    ];
    
    // 读取Session数据
    if (isset($_SESSION['user'])) {
        echo "当前登录用户:{$_SESSION['user']['username']}<br>";
        echo "登录时间:{$_SESSION['user']['login_time']}<br>";
    }
    
    // 删除Session数据(单个键)
    unset($_SESSION['user']['role']);
    
    // 清空所有Session数据(保留会话ID)
    // $_SESSION = [];
    
    // 3. 安全登出(销毁会话+删除Cookie)
    function logout() {
        // 清空Session数据
        $_SESSION = [];
        // 删除会话ID Cookie
        if (ini_get('session.use_cookies')) {
            $cookie_params = session_get_cookie_params();
            setcookie(
                session_name(),
                '',
                time() - 42000, // 过期时间设为过去
                $cookie_params['path'],
                $cookie_params['domain'],
                $cookie_params['secure'],
                $cookie_params['httponly']
            );
        }
        // 销毁服务器端Session数据
        session_destroy();
    }
    
    // 测试登出(需触发,如访问?logout=1)
    if (isset($_GET['logout'])) {
        logout();
        echo "已安全登出,跳转至登录页...";
        header('Refresh: 2; URL=login.php');
        exit;
    }
    ?>
    
  2. 分布式 Session(多服务器部署方案)

    • 问题:单服务器 Session 存储在本地文件,多服务器负载均衡时,用户请求可能分发到不同服务器,导致 Session 失效(如登录状态丢失)。
    • 解决方案:使用 Redis/Memcached 等分布式存储统一管理 Session,所有服务器共享 Session 数据。

    实战配置(Redis 为例):

    <?php
    // 1. 前提:安装php-redis扩展(服务器端)
    // CentOS:dnf install -y php-redis && systemctl restart php-fpm
    // Ubuntu:apt install -y php-redis && systemctl restart php-fpm
    
    // 2. 配置Session存储到Redis(项目入口文件)
    $redis_config = [
        'host' => '127.0.0.1',    // Redis服务器地址
        'port' => 6379,            // Redis端口
        'auth' => 'your_redis_pwd',// Redis密码(无密码可省略)
        'db' => 0,                // Redis数据库编号
        'prefix' => 'APP_SESSID:' // Session键前缀(避免与其他数据冲突)
    ];
    
    // 设置Session存储驱动与路径
    ini_set('session.save_handler', 'redis');
    ini_set(
        'session.save_path',
        "tcp://{$redis_config['host']}:{$redis_config['port']}?auth={$redis_config['auth']}&database={$redis_config['db']}&prefix={$redis_config['prefix']}"
    );
    
    // 3. 启动Session(配置同基础操作)
    session_start([
        'name' => 'APP_SESSID',
        'cookie_httponly' => true,
        'cookie_secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
        'use_strict_mode' => true,
        'gc_maxlifetime' => 1440
    ]);
    
    // 4. 验证Session是否存储到Redis
    $_SESSION['distributed_test'] = [
        'server_ip' => $_SERVER['SERVER_ADDR'],
        'time' => date('Y-m-d H:i:s')
    ];
    
    // 输出Redis中Session的Key(可在Redis客户端用此Key查询)
    echo "Session ID: " . session_id() . "<br>";
    echo "Redis Key: {$redis_config['prefix']}" . session_id() . "<br>";
    
    // 5. 读取Redis中存储的Session数据(验证)
    $redis = new Redis();
    $redis->connect($redis_config['host'], $redis_config['port']);
    $redis->auth($redis_config['auth']);
    $redis->select($redis_config['db']);
    $session_data = $redis->get($redis_config['prefix'] . session_id());
    echo "Redis中Session原始数据: " . $session_data;
    ?>
    
  3. Session 垃圾回收与锁问题

    • 垃圾回收:清理服务器端过期的 Session 数据,避免内存 / 磁盘占用过高。

      • 传统方式:通过session.gc_probability(默认 1)和session.gc_divisor(默认 100)控制回收概率(1/100),每次session_start()可能触发。
      • 生产环境优化:关闭自动回收,通过定时任务手动清理(避免高并发时性能波动)。

      示例(Redis Session 手动清理脚本):

      <?php
      // clean_session.php(定时任务执行)
      $redis_config = [
          'host' => '127.0.0.1',
          'port' => 6379,
          'auth' => 'your_redis_pwd',
          'db' => 0,
          'prefix' => 'APP_SESSID:'
      ];
      
      $redis = new Redis();
      $redis->connect($redis_config['host'], $redis_config['port']);
      $redis->auth($redis_config['auth']);
      $redis->select($redis_config['db']);
      
      // 扫描所有Session Key(批量处理,避免阻塞Redis)
      $iterator = null;
      $expire_time = 1440; // 与session.gc_maxlifetime一致(24分钟)
      $delete_count = 0;
      
      while ($keys = $redis->scan($iterator, $redis_config['prefix'] . '*', 1000)) {
          foreach ($keys as $key) {
              // 检查Key的剩余生存时间(TTL),过期则删除
              $ttl = $redis->ttl($key);
              if ($ttl < 0 || $ttl > $expire_time) {
                  $redis->del($key);
                  $delete_count++;
              }
          }
      }
      
      // 记录清理日志
      $log = date('Y-m-d H:i:s') . " - 清理过期Session数量:{$delete_count}\n";
      file_put_contents('/var/log/session_clean.log', $log, FILE_APPEND);
      echo "清理完成,共删除{$delete_count}个过期Session";
      ?>
      
      • 定时任务配置(Linux Crontab,每 30 分钟执行一次):

        bash

        crontab -e
        # 加入以下内容(路径替换为实际脚本路径)
        */30 * * * * /usr/bin/php /var/www/project/clean_session.php >> /var/log/session_clean.log 2>&1
        
    • Session 锁问题

      • 问题:文件存储 Session 时,PHP 会对 Session 文件加独占锁,同一用户同时发起多个请求(如并行 AJAX、快速点击)时,后续请求会阻塞等待锁释放,导致性能下降。
      • 解决方案
        1. 改用 Redis/Memcached 存储(无文件锁问题,推荐)。
        2. 手动解锁:完成 Session 写入后立即调用session_write_close()释放锁(适合读多写少场景)。

      示例(Session 锁问题解决):

      <?php
      session_start();
      
      // 1. 完成Session写入操作(如记录用户行为)
      $_SESSION['last_visit'] = date('Y-m-d H:i:s');
      
      // 2. 手动关闭Session,释放文件锁(关键步骤)
      session_write_close();
      
      // 3. 后续耗时操作(如数据库查询、文件处理),无锁阻塞
      echo "请求开始:" . date('Y-m-d H:i:s') . "<br>";
      sleep(3); // 模拟耗时操作
      echo "请求结束:" . date('Y-m-d H:i:s') . "<br>";
      ?>
      
(三)Cookie 高级应用
  1. “记住我” 功能实现(自动登录)

    • 原理:用户勾选 “记住我” 时,服务器生成加密的用户标识 Cookie(长期有效,如 7 天),下次访问时自动验证 Cookie 并创建 Session,无需重复登录。
    • 安全要点
      • Cookie 值需加密(如 AES-256-CBC),防止客户端篡改。
      • 关联客户端 IP / 浏览器标识,降低 Cookie 被盗用风险。
      • 提供 “退出所有设备” 功能,支持服务器端失效 Cookie。

    示例(完整 “记住我” 功能):

    <?php
    // 配置常量(生产环境需存储在安全配置文件)
    define('COOKIE_SECRET', 'your_32_byte_secure_key_here_123456'); // 32位AES密钥
    define('REMEMBER_COOKIE', 'app_remember_me');
    define('REMEMBER_EXPIRE', 7 * 24 * 3600); // 7天有效期
    
    // 启动Session
    session_start([
        'name' => 'APP_SESSID',
        'cookie_httponly' => true,
        'cookie_secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
        'use_strict_mode' => true
    ]);
    
    // 加密/解密工具函数
    function encrypt(string $data): string {
        $iv = random_bytes(openssl_cipher_iv_length('aes-256-cbc'));
        $encrypted = openssl_encrypt($data, 'aes-256-cbc', COOKIE_SECRET, OPENSSL_RAW_DATA, $iv);
        return base64_encode($iv . '::' . $encrypted); // 拼接IV(解密需用)
    }
    
    function decrypt(string $data): ?string {
        try {
            $decoded = base64_decode($data);
            list($iv, $encrypted) = explode('::', $decoded, 2);
            return openssl_decrypt($encrypted, 'aes-256-cbc', COOKIE_SECRET, OPENSSL_RAW_DATA, $iv);
        } catch (Exception $e) {
            return null; // 解密失败(如Cookie被篡改)
        }
    }
    
    // 自动登录(通过Cookie验证)
    function autoLogin(): void {
        if (isset($_SESSION['user'])) return; // 已登录则跳过
    
        if (isset($_COOKIE[REMEMBER_COOKIE])) {
            $decrypted = decrypt($_COOKIE[REMEMBER_COOKIE]);
            if ($decrypted) {
                list($user_id, $expire_time, $client_ip) = explode('|', $decrypted);
                // 验证有效期与IP一致性(防Cookie被盗用)
                if (time() <= $expire_time && $client_ip === $_SERVER['REMOTE_ADDR']) {
                    // 模拟从数据库获取用户信息
                    $user = [
                        'id' => (int)$user_id,
                        'username' => 'test_user',
                        'role' => 'user'
                    ];
                    $_SESSION['user'] = $user;
                    session_regenerate_id(true); // 重新生成Session ID,防劫持
                } else {
                    // Cookie失效,清除
                    setcookie(REMEMBER_COOKIE, '', time() - 3600, '/', '', isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on', true);
                }
            }
        }
    }
    
    // 用户登录(含“记住我”逻辑)
    function login(string $username, string $password, bool $remember = false): bool {
        // 模拟数据库验证(正确账号:user / 密码:123456)
        if ($username !== 'user' || $password !== '123456') {
            return false;
        }
    
        // 登录成功,写入Session
        $user = [
            'id' => 1,
            'username' => $username,
            'login_ip' => $_SERVER['REMOTE_ADDR']
        ];
        $_SESSION['user'] = $user;
        session_regenerate_id(true); // 防会话固定攻击
    
        // 勾选“记住我”,设置加密Cookie
        if ($remember) {
            $expire = time() + REMEMBER_EXPIRE;
            $cookie_data = "{$user['id']}|{$expire}|{$_SERVER['REMOTE_ADDR']}";
            $encrypted = encrypt($cookie_data);
            setcookie(
                REMEMBER_COOKIE,
                $encrypted,
                [
                    'expires' => $expire,
                    'path' => '/',
                    'domain' => '',
                    'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
                    'httponly' => true,
                    'samesite' => 'Lax'
                ]
            );
        }
        return true;
    }
    
    // 执行自动登录
    autoLogin();
    
    // 业务逻辑(登录表单处理)
    $message = '';
    $username = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
        $username = trim($_POST['username'] ?? '');
        $password = trim($_POST['password'] ?? '');
        $remember = isset($_POST['remember']) ? true : false;
    
        if (empty($username) || empty($password)) {
            $message = '<span style="color:red">用户名和密码不能为空</span>';
        } elseif (login($username, $password, $remember)) {
            $message = '<span style="color:green">登录成功,3秒后跳转...</span>';
            header('Refresh: 3; URL=' . $_SERVER['PHP_SELF']);
        } else {
            $message = '<span style="color:red">用户名或密码错误</span>';
        }
    }
    ?>
    
    <!-- 登录表单HTML -->
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>用户登录(含记住我)</title>
        <style>
            .container { width: 400px; margin: 50px auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; }
            .form-group { margin: 15px 0; }
            label { display: inline-block; width: 80px; }
            input { padding: 6px; width: 250px; }
            .btn { padding: 8px 20px; cursor: pointer; }
            .message { margin: 20px 0; text-align: center; }
        </style>
    </head>
    <body>
        <div class="container">
            <h2 style="text-align: center;">用户登录</h2>
            <div class="message"><?= $message ?></div>
    
            <?php if (isset($_SESSION['user'])): ?>
                <!-- 已登录状态 -->
                <div style="text-align: center;">
                    <h3>欢迎您,<?= htmlspecialchars($_SESSION['user']['username']) ?></h3>
                    <p>登录IP:<?= $_SESSION['user']['login_ip'] ?></p>
                    <a href="?logout=1" class="btn">登出</a>
                </div>
            <?php else: ?>
                <!-- 未登录状态:登录表单 -->
                <form method="post">
                    <div class="form-group">
                        <label>用户名:</label>
                        <input type="text" name="username" value="<?= htmlspecialchars($username) ?>" required>
                    </div>
                    <div class="form-group">
                        <label>密码:</label>
                        <input type="password" name="password" required>
                    </div>
                    <div class="form-group">
                        <label></label>
                        <label style="width: auto;"><input type="checkbox" name="remember"> 记住我(7天内自动登录)</label>
                    </div>
                    <div class="form-group" style="text-align: center;">
                        <button type="submit" name="login" class="btn">登录</button>
                    </div>
                </form>
            <?php endif; ?>
        </div>
    </body>
    </html>
    
  2. 跨域 Cookie(前后端分离场景)

    • 场景:前端部署在frontend.example.com,后端 API 部署在api.example.com,需实现 Cookie 跨域携带(如认证 Token)。
    • 核心配置
      1. 后端:设置 Cookie 的Domain为父域名(.example.com),开启SameSite=None+Secure
      2. 前端:AJAX 请求设置withCredentials: true(允许携带跨域 Cookie)。
      3. 服务器:配置 CORS(跨域资源共享),允许指定前端域名和Credentials

    示例(前后端完整实现):

    • 后端 API(api.example.com/cookie_cors.php
      <?php
      // 跨域Cookie设置(API服务端)
      $action = $_GET['action'] ?? 'set';
      $cookie_name = 'api_auth_token';
      
      // 加密/解密函数(复用“记住我”功能的实现)
      function encrypt(string $data): string {
          $secret = 'your_32_byte_secure_key_here_123456';
          $iv = random_bytes(openssl_cipher_iv_length('aes-256-cbc'));
          $encrypted = openssl_encrypt($data, 'aes-256-cbc', $secret, OPENSSL_RAW_DATA, $iv);
          return base64_encode($iv . '::' . $encrypted);
      }
      
      function decrypt(string $data): ?string {
          try {
              $secret = 'your_32_byte_secure_key_here_123456';
              $decoded = base64_decode($data);
              list($iv, $encrypted) = explode('::', $decoded, 2);
              return openssl_decrypt($encrypted, 'aes-256-cbc', $secret, OPENSSL_RAW_DATA, $iv);
          } catch (Exception $e) {
              return null;
          }
      }
      
      // 通用Cookie配置(跨域场景)
      $cookie_config = [
          'expires' => time() + 7 * 24 * 3600,
          'path' => '/',
          'domain' => '.example.com', // 父域名,允许子域共享
          'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
          'httponly' => true,
          'samesite' => 'None' // 跨域必须设为None,且配合Secure
      ];
      
      // 设置CORS头(允许前端域名访问)
      $frontend_domain = 'https://frontend.example.com';
      header("Access-Control-Allow-Origin: {$frontend_domain}");
      header("Access-Control-Allow-Credentials: true"); // 允许携带Cookie
      header("Content-Type: application/json");
      
      // 处理不同操作(设置/读取/删除Cookie)
      switch ($action) {
          case 'set':
              $cookie_value = encrypt('user_id:1|role:user');
              setcookie($cookie_name, $cookie_value, $cookie_config);
              echo json_encode([
                  'status' => 'success',
                  'message' => '跨域Cookie已设置'
              ]);
              break;
          case 'get':
              $cookie_value = $_COOKIE[$cookie_name] ?? '';
              $decrypted = $cookie_value ? decrypt($cookie_value) : '未获取到Cookie';
              echo json_encode([
                  'status' => 'success',
                  'data' => $decrypted
              ]);
              break;
          case 'delete':
              setcookie($cookie_name, '', array_merge($cookie_config, ['expires' => time() - 3600]));
              echo json_encode([
                  'status' => 'success',
                  'message' => '跨域Cookie已删除'
              ]);
              break;
      }
      ?>
      
    • 前端页面(frontend.example.com/cors_test.html

      html

      <!DOCTYPE html>
      <html lang="zh-CN">
      <head>
          <meta charset="UTF-8">
          <title>跨域Cookie测试</title>
          <style>
              .btn { margin: 5px; padding: 8px 15px; cursor: pointer; }
              #result { margin-top: 20px; padding: 10px; border: 1px solid #ddd; }
          </style>
      </head>
      <body>
          <h3>跨域Cookie测试(前端)</h3>
          <button class="btn" onclick="setCookie()">设置跨域Cookie</button>
          <button class="btn" onclick="getCookie()">读取跨域Cookie</button>
          <button class="btn" onclick="deleteCookie()">删除跨域Cookie</button>
          <div id="result"></div>
      
          <script>
              const apiUrl = 'https://api.example.com/cookie_cors.php';
      
              // 设置跨域Cookie
              function setCookie() {
                  fetch(`${apiUrl}?action=set`, {
                      method: 'GET',
                      credentials: 'include' // 关键:允许携带跨域Cookie
                  })
                  .then(res => res.json())
                  .then(data => {
                      document.getElementById('result').innerText = JSON.stringify(data, null, 2);
                  })
                  .catch(err => {
                      document.getElementById('result').innerText = '错误:' + err.message;
                  });
              }
      
              // 读取跨域Cookie
              function getCookie() {
                  fetch(`${apiUrl}?action=get`, {
                      method: 'GET',
                      credentials: 'include'
                  })
                  .then(res => res.json())
                  .then(data => {
                      document.getElementById('result').innerText = JSON.stringify(data, null, 2);
                  })
                  .catch(err => {
                      document.getElementById('result').innerText = '错误:' + err.message;
                  });
              }
      
              // 删除跨域Cookie
              function deleteCookie() {
                  fetch(`${apiUrl}?action=delete`, {
                      method: 'GET',
                      credentials: 'include'
                  })
                  .then(res => res.json())
                  .then(data => {
                      document.getElementById('result').innerText = JSON.stringify(data, null, 2);
                  })
                  .catch(err => {
                      document.getElementById('result').innerText = '错误:' + err.message;
                  });
              }
          </script>
      </body>
      </html>
      
    • Nginx CORS 配置(api.example.com

      nginx

      server {
          listen 443 ssl;
          server_name api.example.com;
          ssl_certificate /path/to/cert.pem;
          ssl_certificate_key /path/to/key.pem;
      
          # 处理预检请求(OPTIONS)
          location / {
              if ($request_method = 'OPTIONS') {
                  add_header Access-Control-Allow-Origin 'https://frontend.example.com';
                  add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
                  add_header Access-Control-Allow-Headers 'Content-Type';
                  add_header Access-Control-Allow-Credentials 'true';
                  add_header Content-Length 0;
                  return 204;
              }
      
              # 正常请求CORS配置
              add_header Access-Control-Allow-Origin 'https://frontend.example.com';
              add_header Access-Control-Allow-Credentials 'true';
              proxy_pass http://127.0.0.1:9000; # 转发到PHP-FPM
              include fastcgi_params;
          }
      }
      

三、注意事项(完整整合)

  1. Session 启动时机与错误处理

    • session_start()必须在输出任何内容(HTML 标签、echo、空格)之前调用,否则会触发 “headers already sent” 错误。
    • 解决方案:
      1. 调整代码顺序,确保session_start()在脚本最顶部。
      2. 启用输出缓冲:ob_start()(脚本开头)+ ob_end_flush()(脚本末尾)。
  2. Cookie 安全与拦截问题

    • 敏感数据禁止存储:即使加密,也不建议在 Cookie 中存储密码、Token 等核心敏感数据(优先用 Session)。
    • 常见拦截原因
      • 未设置Secure但使用 HTTPS(现代浏览器默认拦截非 Secure Cookie)。
      • SameSite=Strict但存在跨站请求(如从第三方链接跳转)。
      • 浏览器开启 “隐私模式”(部分浏览器禁用第三方 Cookie)。
      • Cookie 域名与当前域名不匹配(如 Cookie 域名设为.example.com,当前域名是example.net)。
  3. HTTPS 环境适配

    • 生产环境必须启用 HTTPS,并将session.cookie_securecookie_secure设为true,否则:
      • SameSite=None的 Cookie 会被浏览器拦截。
      • Cookie 可能被中间人窃取(HTTP 传输明文)。
    • 配置示例(php.ini):

      ini

      session.cookie_secure = On
      session.cookie_samesite = Lax
      
  4. Session 失效排查流程

    1. 检查 Session ID 一致性:打印session_id(),对比前后端请求中的PHPSESSID Cookie 值。
    2. 检查 Cookie 存储:浏览器 F12→Application→Cookies,确认PHPSESSID是否存在、域名 / 路径是否正确。
    3. 检查存储权限:文件存储时,PHP 进程用户(如 www-data)需有 Session 存储目录(默认/var/lib/php/sessions)的读写权限。
    4. 分布式场景:检查 Redis/Memcached 连接是否正常(用客户端工具测试,如redis-cli)。
    5. 配置检查:确认session.gc_maxlifetime是否过短(建议≥1440 秒),避免 Session 被提前回收。
  5. 高并发场景优化

    • 分布式 Session 优先用 Redis(支持高并发、自动过期),避免用数据库(性能低)。
    • 减少 Session 操作频率:频繁读取的 Session 数据缓存到本地变量(如$user = $_SESSION['user']),避免多次访问$_SESSION
    • 禁用 Session 自动垃圾回收:通过定时任务手动清理,避免高并发时触发回收导致性能波动。

四、实战练习(完整落地)

  1. 实战 1:安全的用户认证系统

    • 需求:实现包含登录、自动登录(记住我)、登出、权限判断的完整认证系统。
    • 技术要求
      1. 登录表单:包含用户名、密码、“记住我” 复选框,表单提交时验证 CSRF Token(参考第 14 课安全内容)。
      2. 登录逻辑:验证用户名密码(模拟数据库),成功后创建 Session,勾选 “记住我” 则设置 7 天有效期的加密 Cookie。
      3. 自动登录:未登录时检查 “记住我” Cookie,验证通过后自动创建 Session。
      4. 权限控制:仅登录用户可访问/user_center.php,未登录则跳转至登录页。
      5. 安全防护:Session ID 定期刷新(每 30 分钟),Cookie 加密,防 XSS/CSRF。
    • 文件结构
      • login.php:登录表单与认证逻辑。
      • user_center.php:用户中心(需登录才能访问)。
      • logout.php:安全登出(销毁 Session + 删除 Cookie)。
  2. 实战 2:分布式 Session 测试

    • 需求:在两台服务器上部署相同项目,实现 Session 共享(用户在服务器 A 登录后,访问服务器 B 仍保持登录状态)。
    • 技术要求
      1. 两台服务器均安装 Redis,并配置主从同步(确保 Session 数据一致)。
      2. 项目中配置 Session 存储到 Redis,统一session.save_pathprefix
      3. 测试流程:服务器 A 登录→访问服务器 B 的用户中心→验证是否保持登录状态。
      4. 编写session_test.php,输出当前服务器 IP 和 Session 数据,验证跨服务器 Session 同步。
  3. 实战 3:前后端分离跨域认证

    • 需求:前端(Vue/React)部署在frontend.example.com,后端 API 部署在api.example.com,实现跨域登录认证。
    • 技术要求
      1. 后端 API:提供/login(登录,设置跨域 Cookie)、/user(获取用户信息,读取 Cookie)接口。
      2. 前端:用 Axios 发送请求,设置withCredentials: true,实现登录后获取用户信息。
      3. 安全验证:后端验证Origin请求头,仅允许frontend.example.com访问,Cookie 启用SameSite=None+Secure
    • 交付物:后端 API 代码、前端请求代码、Nginx CORS 配置文件。

五、课程总结

本课完整覆盖了 Session 与 Cookie 的核心技术,从基础概念到实战落地,再到高级场景(分布式、跨域)与问题解决,重点需掌握:

  1. 安全优先:Session 配置HttpOnly/Secure,Cookie 加密,防 XSS/CSRF/ 劫持。
  2. 场景适配:敏感数据用 Session,长期状态用 Cookie(如 “记住我”),跨域场景用SameSite=None+CORS。
  3. 问题解决:Session 锁用 Redis 或session_write_close(),Cookie 拦截检查Secure/SameSite/ 域名配置。

Session 与 Cookie 是 Web 开发中用户状态保持的核心技术,后续学习框架(如 Laravel 的Session/Cookie facade)、单点登录(SSO)均需基于这些知识点,建议通过实战反复验证,熟练掌握安全配置与问题排查方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anson Jiang

感谢客官老爷打赏的咖啡钱:-)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值