PHP 会话管理
本章将介绍 PHP 的 Cookie 和 Session 机制,用于在多个页面间保持用户状态。
Cookie
什么是 Cookie?
Cookie 是存储在用户浏览器中的小型文本文件,用于跟踪用户信息。Cookie 随每次 HTTP 请求自动发送到服务器。
创建 Cookie
<?php
// setcookie(name, value, expire, path, domain, secure, httponly)
// 基本设置
setcookie("username", "张三");
// 设置过期时间(1小时后)
setcookie("token", "abc123", time() + 3600);
// 设置过期时间(7天后)
setcookie("remember", "yes", time() + 7 * 24 * 60 * 60);
// 设置路径(整个网站可用)
setcookie("pref", "dark", time() + 3600, "/");
// 设置域名(子域名也可用)
setcookie("lang", "zh", time() + 3600, "/", ".example.com");
// 仅 HTTPS 传输
setcookie("secure_token", "value", time() + 3600, "/", "", true);
// 仅 HTTP 访问(JavaScript 无法读取,防 XSS)
setcookie("session_id", "xyz", time() + 3600, "/", "", false, true);
// 完整设置
setcookie(
"user_pref", // 名称
"dark_mode", // 值
time() + 30 * 24 * 3600, // 过期时间
"/", // 路径
"example.com", // 域名
true, // 仅 HTTPS
true // 仅 HTTP
);
?>
读取 Cookie
<?php
// 检查 Cookie 是否存在
if (isset($_COOKIE['username'])) {
echo "欢迎回来," . $_COOKIE['username'];
}
// 读取 Cookie 值
$username = $_COOKIE['username'] ?? '游客';
// 遍历所有 Cookie
foreach ($_COOKIE as $name => $value) {
echo "$name: $value<br>";
}
?>
删除 Cookie
<?php
// 删除 Cookie(设置过期时间为过去)
setcookie("username", "", time() - 3600);
// 删除时需要与创建时相同的参数
setcookie("user_pref", "", time() - 3600, "/", "example.com", true, true);
// 清除所有 Cookie
foreach ($_COOKIE as $name => $value) {
setcookie($name, "", time() - 3600);
}
?>
Cookie 注意事项
<?php
// Cookie 必须在输出之前设置
// 错误示例:
// echo "Hello";
// setcookie("test", "value"); // 会报错
// 正确示例:
setcookie("test", "value");
echo "Hello";
// Cookie 值限制
// - 单个 Cookie 最大 4KB
// - 每个域名最多约 50 个 Cookie
// - 总数最多约 300 个
// 存储数组(使用序列化)
$data = ["name" => "张三", "age" => 25];
setcookie("user_data", base64_encode(serialize($data)), time() + 3600);
// 读取数组
$data = unserialize(base64_decode($_COOKIE['user_data']));
?>
Session
什么是 Session?
Session 存储在服务器端,通过 Session ID 与用户关联。Session ID 通常存储在 Cookie 中。
启动 Session
<?php
// 启动 Session(必须在输出之前)
session_start();
// PHP 7+ 可以检查 Session 状态
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
?>
使用 Session
<?php
session_start();
// 设置 Session 值
$_SESSION['username'] = "张三";
$_SESSION['user_id'] = 123;
$_SESSION['logged_in'] = true;
// 读取 Session 值
echo $_SESSION['username']; // 张三
// 检查 Session 是否存在
if (isset($_SESSION['logged_in']) && $_SESSION['logged_in']) {
echo "已登录";
}
// 删除单个 Session
unset($_SESSION['username']);
// 删除所有 Session
$_SESSION = [];
// 销毁 Session
session_destroy();
?>
Session 配置
<?php
// 在 session_start() 之前配置
// 设置 Session 名称
session_name("MY_SESSION");
// 设置 Session 存储路径
session_save_path("/tmp/sessions");
// 设置 Cookie 参数
session_set_cookie_params([
'lifetime' => 3600, // 生命周期
'path' => '/', // 路径
'domain' => 'example.com', // 域名
'secure' => true, // 仅 HTTPS
'httponly' => true, // 仅 HTTP
'samesite' => 'Strict' // CSRF 防护
]);
session_start();
// 或在 php.ini 中配置
// session.name = MY_SESSION
// session.cookie_lifetime = 3600
// session.cookie_secure = 1
// session.cookie_httponly = 1
// session.cookie_samesite = Strict
?>
Session ID
<?php
session_start();
// 获取当前 Session ID
echo session_id();
// 设置新的 Session ID(必须在 session_start 之前)
session_id("custom_session_id_123");
// 重新生成 Session ID(防止会话固定攻击)
session_regenerate_id(true); // true 删除旧 Session
// 检查 Session ID 是否有效
if (preg_match('/^[a-f0-9]{32}$/', session_id())) {
echo "Session ID 有效";
}
?>
Session 存储
<?php
// 默认文件存储
// Session 文件存储在 session.save_path 指定的目录
// 自定义 Session 处理器
class DatabaseSessionHandler implements SessionHandlerInterface {
private $pdo;
public function open($path, $name): bool {
$this->pdo = new PDO('mysql:host=localhost;dbname=sessions', 'user', 'pass');
return true;
}
public function close(): bool {
return true;
}
public function read($id): string {
$stmt = $this->pdo->prepare('SELECT data FROM sessions WHERE id = ?');
$stmt->execute([$id]);
return $stmt->fetchColumn() ?: '';
}
public function write($id, $data): bool {
$stmt = $this->pdo->prepare(
'INSERT INTO sessions (id, data, timestamp) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE data = ?, timestamp = ?'
);
$time = time();
$stmt->execute([$id, $data, $time, $data, $time]);
return true;
}
public function destroy($id): bool {
$stmt = $this->pdo->prepare('DELETE FROM sessions WHERE id = ?');
return $stmt->execute([$id]);
}
public function gc($max_lifetime): int {
$stmt = $this->pdo->prepare('DELETE FROM sessions WHERE timestamp < ?');
$stmt->execute([time() - $max_lifetime]);
return $stmt->rowCount();
}
}
$handler = new DatabaseSessionHandler();
session_set_save_handler($handler, true);
session_start();
?>
登录系统示例
<?php
session_start();
// 用户登录
function login(string $username, string $password): bool {
// 验证用户(示例)
$users = [
'admin' => password_hash('admin123', PASSWORD_DEFAULT),
'user' => password_hash('user123', PASSWORD_DEFAULT)
];
if (!isset($users[$username])) {
return false;
}
if (!password_verify($password, $users[$username])) {
return false;
}
// 登录成功,设置 Session
session_regenerate_id(true); // 防止会话固定攻击
$_SESSION['logged_in'] = true;
$_SESSION['username'] = $username;
$_SESSION['login_time'] = time();
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
return true;
}
// 检查登录状态
function isLoggedIn(): bool {
if (!isset($_SESSION['logged_in']) || !$_SESSION['logged_in']) {
return false;
}
// 验证 User-Agent(防止会话劫持)
if ($_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
session_destroy();
return false;
}
// 可选:验证 IP
// if ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) {
// session_destroy();
// return false;
// }
// 检查登录超时(30分钟)
if (time() - $_SESSION['login_time'] > 1800) {
session_destroy();
return false;
}
// 更新最后活动时间
$_SESSION['last_activity'] = time();
return true;
}
// 用户登出
function logout(): void {
$_SESSION = [];
// 删除 Session Cookie
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params["path"],
$params["domain"],
$params["secure"],
$params["httponly"]
);
}
session_destroy();
}
// 使用示例
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if (login($username, $password)) {
header('Location: dashboard.php');
exit;
} else {
$error = "用户名或密码错误";
}
}
?>
"记住我"功能
<?php
// 生成安全的记住令牌
function generateRememberToken(): string {
return bin2hex(random_bytes(32));
}
// 设置记住 Cookie
function setRememberMe(int $userId): void {
$token = generateRememberToken();
$hash = hash('sha256', $token);
// 存储到数据库
// $db->query("INSERT INTO remember_tokens (user_id, token_hash) VALUES (?, ?)", [$userId, $hash]);
// 设置 Cookie(30天)
setcookie(
'remember_token',
$token,
time() + 30 * 24 * 60 * 60,
'/',
'',
true, // secure
true // httponly
);
}
// 验证记住令牌
function checkRememberToken(): ?int {
if (!isset($_COOKIE['remember_token'])) {
return null;
}
$token = $_COOKIE['remember_token'];
$hash = hash('sha256', $token);
// 从数据库查找
// $row = $db->query("SELECT user_id FROM remember_tokens WHERE token_hash = ?", [$hash]);
// if ($row) {
// return $row['user_id'];
// }
return null;
}
// 清除记住令牌
function clearRememberToken(): void {
if (isset($_COOKIE['remember_token'])) {
// 从数据库删除
// $hash = hash('sha256', $_COOKIE['remember_token']);
// $db->query("DELETE FROM remember_tokens WHERE token_hash = ?", [$hash]);
// 删除 Cookie
setcookie('remember_token', '', time() - 3600, '/', '', true, true);
}
}
?>
Session vs Cookie
| 特性 | Session | Cookie |
|---|---|---|
| 存储位置 | 服务器 | 浏览器 |
| 安全性 | 较高 | 较低 |
| 存储容量 | 无限制 | 约 4KB |
| 过期时间 | 可配置 | 可配置 |
| 跨域支持 | 不支持 | 支持 |
小结
本章我们学习了:
- Cookie 的创建、读取和删除
- Session 的启动和使用
- Session 配置和安全选项
- 登录系统实现
- "记住我"功能
下一章我们将学习命名空间。