PHP 零基础教程

PHP Session 会话控制

PHP Session(会话)提供了一种在多个页面请求之间保持用户专属数据的机制。与 $_GET$_POST 仅能处理单次请求的数据不同,$_SESSION 允许应用程序在用户浏览网站时“记住”他们。这种能力是实现用户身份验证(登录)、购物车以及个性化推荐等功能的基础。

1. 什么是 PHP Session

在 PHP 中,Session 会为每个用户创建一个唯一的标识符,通常称为 Session ID。这个 ID 通常作为 Cookie 存储在用户的浏览器中。当用户向服务器发起后续请求时,他们的浏览器会将这个 Session ID 发送回来,允许 PHP 检索相关的 Session 数据。这些数据存储在服务器端,而不是客户端,这使得 Session 在处理敏感信息时比普通的 Cookie 安全得多。

Session 的核心特征包括:

  • 服务端存储: Session 数据保存在服务器上,这比客户端存储(如 Cookie)更安全,适合存放敏感信息。
  • 临时持久性: Session 数据在单个用户的多次页面请求之间持续存在,但通常会在用户关闭浏览器或经过一段预设的不活动时间后被销毁。
  • 唯一标识: 每个活动的 Session 都由一个唯一的 Session ID 进行标识。

2. 开启一个 Session

在使用任何 Session 变量之前,必须使用 session_start() 函数显式地开启 Session。这个函数会初始化一个新的 Session,或者根据客户端传来的 Session ID 恢复一个已存在的 Session。

重要规则: session_start() 必须在脚本的最开始处调用,在向浏览器发送任何输出(包括空格、换行或 HTML 标签)之前。如果在 session_start() 之前有输出,将会引发错误,导致 Session 无法正常初始化。

<?php
// 务必在脚本的最开始调用 session_start()。
// 这将初始化会话或恢复当前会话。
session_start();

// 在 session_start() 之后可以编写其他 PHP 代码和 HTML。
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Session 示例</title>
</head>
<body>
    <h1>欢迎!</h1>
</body>
</html>

3. 存储与读取 Session 数据

一旦调用了 session_start()$_SESSION 超全局数组就可以使用了。这个关联数组可以用来存储和检索与当前用户会话相关的任何数据。存入 $_SESSION 的数据可以是任何 PHP 数据类型,包括字符串、数字、布尔值、数组或对象。

3.1 存储数据

要存储数据,只需为 $_SESSION 数组中的一个键(Key)赋值即可:

<?php
session_start();

// 在 session 中存储用户名
$_SESSION['username'] = 'JohnDoe';

// 存储购物车中的商品数组
$_SESSION['cart'] = ['item_id' => 101, 'quantity' => 2];

echo "Session 数据已存储。";
?>

3.2 读取数据

要读取数据,只需通过键名访问 $_SESSION 数组:

<?php
session_start();

// 在尝试访问前,先检查 session 中是否设置了用户名
if (isset($_SESSION['username'])) {
    $username = $_SESSION['username'];
    // 输出用户数据时,使用 htmlspecialchars 防御 XSS 攻击
    echo "欢迎回来," . htmlspecialchars($username) . "!<br>"; 
} else {
    echo "在 session 中未找到用户名。<br>";
}

// 获取购物车商品
if (isset($_SESSION['cart'])) {
    echo "你的购物车包含:<br>";
    foreach ($_SESSION['cart'] as $key => $value) {
        echo htmlspecialchars($key) . ": " . htmlspecialchars($value) . "<br>";
    }
} else {
    echo "你的购物车是空的。<br>";
}
?>

4. 修改与删除 Session 数据

通过给 $_SESSION 中已存在的键赋予新值,就可以修改 Session 数据。要删除特定的 Session 变量,可以使用 unset() 函数。如果要销毁与当前 Session 关联的所有数据并使 Session ID 失效,需要组合使用 session_unset()session_destroy()

修改 Session 变量:

<?php
session_start();

$_SESSION['page_views'] = isset($_SESSION['page_views']) ? $_SESSION['page_views'] + 1 : 1;
echo "你已经浏览了 " . $_SESSION['page_views'] . " 个页面。<br>";

// 修改用户名
$_SESSION['username'] = 'JaneDoe';
echo "用户名已更新为:" . $_SESSION['username'] . "<br>";
?>

删除特定的 Session 变量:

<?php
session_start();

// 假设之前已经设置了 'cart'
$_SESSION['cart'] = ['item_id' => 200, 'quantity' => 1];

echo "unset 之前的购物车:";
print_r($_SESSION['cart']);
echo "<br>";

unset($_SESSION['cart']); // 仅移除 'cart' 变量

echo "unset 之后的购物车:";
if (!isset($_SESSION['cart'])) {
    echo "购物车变量已不再设置。";
}
echo "<br>";
?>

销毁所有 Session 数据并彻底结束会话:
要完全注销用户或重置其会话,必须取消设置所有 Session 变量,然后销毁 Session 本身。

  • session_unset(): 从 $_SESSION 超全局数组中移除所有变量。它不会销毁 Session 本身或 Session Cookie。
  • session_destroy(): 销毁注册到会话的所有数据。这实际上是从服务器上删除了会话文件。它不会重置 $_SESSION 数组,因此为了干净地注销,通常应在 session_destroy() 之前调用 session_unset()

此外,在客户端显式删除 Session Cookie 也是一个好习惯,特别是当系统依赖 Cookie 来传递 Session ID 时。这通常通过设置一个同名但值为空、且过期时间在过去的 Cookie 来实现。

<?php
session_start();

// 假设用户已登录,他们的数据在 $_SESSION 中
$_SESSION['username'] = 'LoggedInUser';
$_SESSION['user_id'] = 123;

echo "注销前:<br>";
print_r($_SESSION);
echo "<br>";

// 1. 清空所有 session 变量
session_unset();

// 2. 销毁 session
session_destroy();

// 3. 可选:删除 session cookie 以彻底清理
// 获取 session cookie 的名称(默认为 PHPSESSID)
$session_name = session_name();
if (isset($_COOKIE[$session_name])) {
    // 将 cookie 设置为在过去的时间过期
    setcookie($session_name, '', time() - 3600, '/');
}

echo "注销后:<br>";
if (empty($_SESSION)) { // 在 session_unset 和 session_destroy 之后 $_SESSION 可能仍然是一个空数组
    echo "Session 变量已清空。Session 已销毁。";
}
echo "<br>";

// 销毁后尝试访问 $_SESSION 变量将得到 null/undefined
echo "销毁后尝试访问用户名:" . ($_SESSION['username'] ?? '未设置') . "<br>";
?>

5. 实战演练:用户登录系统

用户身份验证是 $_SESSION 的经典应用场景。当用户成功登录后,他们的身份标识(以及部分权限信息)可以存储在 Session 中。这样就避免了他们在访问每个页面时都需要重新输入密码。

考虑一个简单的登录系统,包含三个核心文件:login.phpauthenticate.phpdashboard.php,以及一个注销脚本 logout.php

1. login.php (显示登录表单)

<?php
session_start(); // 开启 session 以检查现有的登录状态

// 如果用户已经登录,将其重定向到仪表盘页面
if (isset($_SESSION['user_id'])) {
    header('Location: dashboard.php');
    exit();
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
    <h2>登录页面</h2>
    <?php
    // 如果存在错误消息(例如认证失败),则显示它
    if (isset($_SESSION['error_message'])) {
        echo '<p style="color: red;">' . htmlspecialchars($_SESSION['error_message']) . '</p>';
        unset($_SESSION['error_message']); // 显示后清除消息
    }
    ?>
    <form action="authenticate.php" method="post">
        <label for="username">用户名:</label><br>
        <input type="text" id="username" name="username" required><br><br>
        
        <label for="password">密码:</label><br>
        <input type="password" id="password" name="password" required><br><br>
        
        <input type="submit" value="登录">
    </form>
</body>
</html>

2. authenticate.php (处理登录凭证)

<?php
session_start(); // 始终开启 session

// 检查表单是否使用 POST 方法提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'] ?? '';
    $password = $_POST['password'] ?? '';

    // 模拟用户认证(在真实应用中,你需要查询数据库)
    // 为了演示,这里假设有效的凭证是 'admin' 和 'password123'
    if ($username === 'admin' && $password === 'password123') {
        // 认证成功:将用户数据存储在 session 中
        $_SESSION['user_id'] = 1; // 示例用户 ID
        $_SESSION['username'] = $username;
        $_SESSION['role'] = 'administrator'; // 示例角色

        // 重定向到受保护的页面(例如仪表盘)
        header('Location: dashboard.php');
        exit();
    } else {
        // 认证失败:在 session 中存储错误消息
        $_SESSION['error_message'] = '用户名或密码无效。';
        // 重定向回登录页面
        header('Location: login.php');
        exit();
    }
} else {
    // 如果未通过 POST 直接访问,则重定向到登录页面
    header('Location: login.php');
    exit();
}
?>

3. dashboard.php (受保护的页面,登录后方可访问)

<?php
session_start(); // 开启 session

// 检查用户是否已登录(即 session 中是否设置了 'user_id')
if (!isset($_SESSION['user_id'])) {
    // 如果未登录,重定向到登录页面
    $_SESSION['error_message'] = '你必须登录才能查看此页面。';
    header('Location: login.php');
    exit();
}

// 用户已登录,检索 session 数据
$username = $_SESSION['username'] ?? '访客';
$role = $_SESSION['role'] ?? 'user';
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>仪表盘</title>
</head>
<body>
    <h2>欢迎来到你的仪表盘,<?php echo htmlspecialchars($username); ?>!</h2>
    <p>你的角色:<?php echo htmlspecialchars($role); ?></p>
    <p><a href="logout.php">注销</a></p>
</body>
</html>

4. logout.php (处理用户注销)

<?php
session_start(); // 开启 session

// 清空所有 session 变量
session_unset();

// 销毁 session
session_destroy();

// 可选:删除 session cookie
$session_name = session_name();
if (isset($_COOKIE[$session_name])) {
    setcookie($session_name, '', time() - 3600, '/'); // 将过期时间设置在过去
}

// 重定向到登录页面
header('Location: login.php');
exit();
?>

6. Session 配置与安全最佳实践

PHP Session 提供了多种配置选项,这些选项直接影响其行为和安全性。可以在 php.ini 中设置,或者动态使用 ini_set()session_set_cookie_params() 来配置。

6.1 常见的 Session 配置指令

  • session.save_handler: 指定用于存储和检索 Session 数据的处理器。默认是 files(作为文件存储在服务器上),在分布式系统中常使用 memcacheredis
  • session.use_cookies: 决定是否使用 Cookie 在客户端存储 Session ID。默认开启。强烈建议使用 Cookie
  • session.use_only_cookies: 防止攻击者在 URL 中传递 Session ID(URL 重写),这是防范 Session 固定攻击 (Session Fixation) 的关键。在现代 PHP 中默认开启。
  • session.cookie_lifetime: Session Cookie 的存活时间(秒)。0 表示“直到关闭浏览器”。
  • session.cookie_secure: 如果为 true,Session Cookie 将仅通过 HTTPS 连接发送。对于生产环境极其重要。
  • session.cookie_httponly: 如果为 true,Cookie 只能通过 HTTP 协议访问,JavaScript 无法读取。这有效缓解了 XSS 跨站脚本攻击 窃取 Session 的风险。

动态配置示例:

<?php
// 在 session_start() 之前设置 session cookie 参数
// 这个例子使 session cookie 有效期为 1 小时,
// 全站可访问,安全(仅限 HTTPS),并且是 HTTP-Only 的。
session_set_cookie_params([
    'lifetime' => 3600, // 1 小时
    'path' => '/',
    'domain' => '.yourdomain.com', // 替换为你的真实域名
    'secure' => true, // 仅通过 HTTPS 发送
    'httponly' => true, // JavaScript 无法访问
    'samesite' => 'Lax' // 帮助防范 CSRF 攻击
]);

session_start();
// ... 剩下的 session 逻辑
?>

6.2 核心安全最佳实践

  • 权限变更时重新生成 Session ID: 在用户成功登录后,或权限发生重大变更时,务必使用 session_regenerate_id(true)。这能有效防范 Session 固定攻击。
<?php
session_start();
// ... 登录成功的逻辑 ...
$_SESSION['user_id'] = $user_id;

// 重新生成 ID,参数 true 表示删除旧的 session 文件
session_regenerate_id(true); 
?>
  • 存储最少的敏感数据: 避免在 $_SESSION 中直接存储高度敏感的数据(如明文密码、银行卡号)。
  • 验证 Session 数据: 就像对待 $_GET$_POST 一样,始终验证和清理从 $_SESSION 检索到的数据。