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.php、authenticate.php 和 dashboard.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(作为文件存储在服务器上),在分布式系统中常使用memcache或redis。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检索到的数据。