PHP 会话与 Cookie
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
HTTP 是无状态协议:每个请求互相独立。要「记住用户」,就得靠 Cookie(存在浏览器)与 Session(存在服务器)。
两者的分工
| Cookie | Session | |
|---|---|---|
| 存储位置 | 浏览器 | 服务器(默认文件系统) |
| 容量 | 约 4 KB | 受服务器限制,可存较多 |
| 安全性 | 用户可见可改,不可信 | 用户只拿到 ID,内容不可见 |
| 典型用途 | 记住偏好、非敏感标记 | 登录态、购物车、权限信息 |
Session 依赖 Cookie:Session ID 默认通过名为 PHPSESSID 的 Cookie 传递。
使用 Session
<?php
declare(strict_types=1);
session_start();
// 写入
$_SESSION["user_id"] = 42;
$_SESSION["cart"] = ["item_1" => 2];
// 读取
$userId = $_SESSION["user_id"] ?? null;
// 删除单个键
unset($_SESSION["cart"]);
session_start() 必须在任何输出之前调用,否则会报「headers already sent」。常见踩坑是文件开头有 BOM 或 ?> 之后有多余换行。
销毁登录态
退出登录要彻底,三步缺一不可:
session_start();
$_SESSION = []; // 1. 清空数据
if (ini_get("session.use_cookies")) { // 2. 删除会话 Cookie
$params = session_get_cookie_params();
setcookie(
session_name(),
"",
[
"expires" => time() - 42000,
"path" => $params["path"],
"domain" => $params["domain"],
"secure" => $params["secure"],
"httponly" => $params["httponly"],
"samesite" => $params["samesite"],
]
);
}
session_destroy(); // 3. 销毁服务端会话
只调用 session_destroy() 而不清空 $_SESSION,本次请求里的数据仍可访问。
会话固定攻击与 ID 轮换
登录成功后必须重新生成 Session ID,否则攻击者可以诱导用户使用已知 ID 登录后劫持会话:
session_start();
if (password_verify($password, $hash)) {
session_regenerate_id(true); // true = 同时删除旧会话文件
$_SESSION["user_id"] = $user["id"];
$_SESSION["logged_in_at"] = time();
}
设置 Cookie
PHP 7.3+ 支持数组形式的选项(推荐,可读性好):
setcookie("theme", "dark", [
"expires" => time() + 86400 * 30, // 30 天后过期
"path" => "/",
"domain" => "", // 当前域
"secure" => true, // 仅 HTTPS 传输
"httponly" => true, // JS 不可读取
"samesite" => "Lax", // 跨站请求限制
]);
各选项的作用:
secure:只在 HTTPS 下发送。本地 HTTP 开发时设为false,上线必须true。httponly:JS 无法通过document.cookie读取,缓解 XSS 窃取。samesite:Lax(推荐默认,允许常规导航带 Cookie)、Strict(最严,跨站一律不带)、None(跨站也带,必须同时secure)。
删除 Cookie 用过期时间:
setcookie("theme", "", ["expires" => time() - 3600, "path" => "/"]);
读取 Cookie
$theme = $_COOKIE["theme"] ?? "light";
// 只允许白名单值,防止被用户改成任意内容
$theme = in_array($theme, ["light", "dark"], true) ? $theme : "light";
Cookie 内容用户可随意修改,取出来后必须校验,绝不能当作可信数据源(例如不要把用户 ID 存 Cookie 来判断身份)。
Session 存储与配置
默认存在服务器的临时目录。想改存储位置或方式:
// 改存储目录(脚本内设置,需在 session_start 前)
session_save_path(__DIR__ . "/storage/sessions");
session_start();
常用 php.ini 配置:
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = Lax
session.gc_maxlifetime = 1440 ; 会话数据存活秒数
session.use_strict_mode = 1 ; 只接受自己创建的会话 ID,防会话固定
session.use_strict_mode = 1 建议始终开启。
一个完整例子:登录与访问控制
<?php
declare(strict_types=1);
require __DIR__ . "/bootstrap.php"; // 假设这里完成了 session 配置
session_start();
function requireLogin(): void
{
if (empty($_SESSION["user_id"])) {
header("Location: /login.php", true, 303);
exit;
}
}
function login(int $userId): void
{
session_regenerate_id(true);
$_SESSION["user_id"] = $userId;
$_SESSION["logged_in_at"] = time();
}
function logout(): void
{
$_SESSION = [];
session_destroy();
}
requireLogin();
echo "当前用户 ID:" . (int) $_SESSION["user_id"];
访问控制放在每个需要保护的入口,而不是只放在登录页:用户可以直接访问任意 URL。
常见错误与注意事项
session_start()前有输出:报 headers already sent,检查 BOM 与?>后的空行。- 登录后不轮换 Session ID:会话固定攻击风险,用
session_regenerate_id(true)。 - 退出时只
session_destroy():$_SESSION本次请求仍可用,先清空再销毁。 - 把敏感数据存 Cookie:用户可见可改,敏感信息只存 Session。
- Cookie 值不校验直接用:用户可伪造,用白名单过滤。
- 生产环境不开
secure/httponly:明文传输或被 JS 窃取。 - 只在登录页做权限检查:每个受保护页面都要独立校验。
小结
- Cookie 存浏览器、不可信、约 4 KB;Session 存服务器、通过 Session ID 关联。
session_start()必须在任何输出之前。- 登录成功
session_regenerate_id(true);退出时清空$_SESSION+ 删 Cookie +session_destroy()。 setcookie上线时给secure、httponly、samesite。- 权限校验放在每个受保护入口,不只在登录流程里。