Cookies基础
Cookies是存储在用户计算机上的小文本文件,用于在多次页面请求之间保持用户信息。由于HTTP协议是无状态的,Cookies提供了一种在客户端存储数据的机制。
Cookie的工作原理
当服务器向浏览器发送响应时,可以包含Set-Cookie头部,浏览器会存储这些Cookie。在后续的请求中,浏览器会自动将Cookie发送回服务器。
设置Cookie
使用setcookie()函数设置Cookie。必须在输出任何内容之前调用此函数。
设置Cookie
<?php
// 设置一个简单的Cookie
setcookie("user", "张三", time() + (86400 * 30), "/"); // 30天过期
// 设置带更多选项的Cookie
setcookie("theme", "dark", [
'expires' => time() + 86400 * 30,
'path' => '/',
'domain' => 'example.com',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);
// 设置数组Cookie
setcookie("user[username]", "张三");
setcookie("user[email]", "zhangsan@example.com");
?>
读取Cookie
使用$_COOKIE超全局变量读取Cookie。
读取Cookie
<?php
if (isset($_COOKIE["user"])) {
echo "欢迎回来, " . htmlspecialchars($_COOKIE["user"]) . "!";
} else {
echo "欢迎新用户!";
}
// 读取数组Cookie
if (isset($_COOKIE['user'])) {
echo "用户名: " . htmlspecialchars($_COOKIE['user']['username']);
echo "邮箱: " . htmlspecialchars($_COOKIE['user']['email']);
}
?>
删除Cookie
通过设置过期时间为过去的时间来删除Cookie。
删除Cookie
<?php
// 删除Cookie
setcookie("user", "", time() - 3600, "/");
// 删除数组Cookie
setcookie("user[username]", "", time() - 3600);
setcookie("user[email]", "", time() - 3600);
?>
Cookie的限制和注意事项
- 每个Cookie最大4KB
- 每个域名最多20个Cookie(不同浏览器可能不同)
- Cookie存储在客户端,用户可以查看和修改
- 敏感数据不应存储在Cookie中
- 设置适当的过期时间
- 使用安全标志(Secure, HttpOnly, SameSite)
Session基础
Session是一种在服务器端存储用户信息的机制,比Cookie更安全。Session数据存储在服务器上,客户端只保存一个Session ID。
Session的工作原理
当Session启动时,PHP会生成一个唯一的Session ID,并通过Cookie发送给客户端。在后续请求中,客户端会发送这个Session ID,PHP根据ID恢复对应的Session数据。
启动Session
启动Session
<?php
// 启动Session
session_start();
// 设置Session变量
$_SESSION["username"] = "张三";
$_SESSION["user_id"] = 12345;
$_SESSION["last_login"] = date("Y-m-d H:i:s");
$_SESSION["preferences"] = [
"theme" => "dark",
"language" => "zh-CN",
"timezone" => "Asia/Shanghai"
];
echo "Session已启动!";
?>
读取Session数据
读取Session
<?php
// 启动Session
session_start();
// 读取Session数据
if (isset($_SESSION["username"])) {
echo "欢迎回来, " . htmlspecialchars($_SESSION["username"]) . "!<br>";
echo "用户ID: " . $_SESSION["user_id"] . "<br>";
echo "最后登录: " . $_SESSION["last_login"] . "<br>";
// 读取数组Session数据
echo "主题: " . htmlspecialchars($_SESSION["preferences"]["theme"]) . "<br>";
echo "语言: " . htmlspecialchars($_SESSION["preferences"]["language"]) . "<br>";
} else {
echo "请先登录!";
}
?>
销毁Session
销毁Session
<?php
// 启动Session
session_start();
// 销毁所有Session数据
session_unset();
session_destroy();
// 删除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"]
);
}
echo "Session已销毁!";
?>
Session垃圾回收
PHP会自动清理过期的Session文件。可以通过以下配置调整垃圾回收行为:
- session.gc_maxlifetime - Session过期时间(秒)
- session.gc_probability - 垃圾回收概率分子
- session.gc_divisor - 垃圾回收概率分母
Session配置
可以通过session_set_cookie_params()或php.ini文件配置Session。
Session配置选项
Session配置
<?php
// 设置Session Cookie参数
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => 'example.com',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);
// 设置Session垃圾回收时间(24小时)
ini_set('session.gc_maxlifetime', 86400);
// 自定义Session存储路径
ini_set('session.save_path', '/custom/session/path');
// 启动Session
session_start();
?>
Session安全设置
Session安全
<?php
// 增强Session安全性
ini_set('session.use_strict_mode', 1);
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Strict');
// 定期更换Session ID
session_start();
if (mt_rand(1, 100) <= 5) {
session_regenerate_id(true);
}
// 自定义Session ID生成
session_id(bin2hex(random_bytes(16)));
?>
自定义Session处理器
可以自定义Session处理器,将Session数据存储到数据库、Redis等。
数据库Session处理器
<?php
class DBSessionHandler implements SessionHandlerInterface
{
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function open($savePath, $sessionName) {
return true;
}
public function close() {
return true;
}
public function read($sessionId) {
$stmt = $this->pdo->prepare("SELECT session_data FROM sessions WHERE session_id = ?");
$stmt->execute([$sessionId]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ? $result['session_data'] : '';
}
public function write($sessionId, $sessionData) {
$stmt = $this->pdo->prepare("REPLACE INTO sessions (session_id, session_data, last_accessed) VALUES (?, ?, ?)");
return $stmt->execute([$sessionId, $sessionData, time()]);
}
public function destroy($sessionId) {
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE session_id = ?");
return $stmt->execute([$sessionId]);
}
public function gc($maxLifetime) {
$stmt = $this->pdo->prepare("DELETE FROM sessions WHERE last_accessed < ?");
return $stmt->execute([time() - $maxLifetime]);
}
}
// 使用自定义Session处理器
$pdo = new PDO("mysql:host=localhost;dbname=test", "username", "password");
$handler = new DBSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();
?>
Cookie和Session的实际应用
用户登录系统
登录系统
<?php
session_start();
// 登录处理
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
$username = trim($_POST['username']);
$password = $_POST['password'];
// 验证用户(这里简化了验证过程)
if (authenticateUser($username, $password)) {
// 设置Session
$_SESSION['user_id'] = getUserId($username);
$_SESSION['username'] = $username;
$_SESSION['logged_in'] = true;
$_SESSION['login_time'] = time();
// 设置"记住我"Cookie
if (isset($_POST['remember_me'])) {
$token = bin2hex(random_bytes(32));
setRememberMeToken($_SESSION['user_id'], $token);
setcookie('remember_me', $token, time() + (86400 * 30), '/', '', true, true);
}
header('Location: dashboard.php');
exit;
} else {
$error = "用户名或密码错误";
}
}
// 自动登录(通过"记住我"Cookie)
if (!isset($_SESSION['logged_in']) && isset($_COOKIE['remember_me'])) {
$user_id = validateRememberMeToken($_COOKIE['remember_me']);
if ($user_id) {
$_SESSION['user_id'] = $user_id;
$_SESSION['username'] = getUsername($user_id);
$_SESSION['logged_in'] = true;
$_SESSION['login_time'] = time();
}
}
function authenticateUser($username, $password) {
// 实际应用中应该查询数据库验证用户
return ($username === 'admin' && $password === 'password');
}
function getUserId($username) {
// 根据用户名获取用户ID
return 1;
}
function setRememberMeToken($user_id, $token) {
// 将token存储到数据库
// 实际应用中应该哈希token并设置过期时间
}
function validateRememberMeToken($token) {
// 验证token并返回用户ID
// 实际应用中应该查询数据库验证token
return 1;
}
function getUsername($user_id) {
// 根据用户ID获取用户名
return 'admin';
}
?>
<!DOCTYPE html>
<html>
<head>
<title>登录</title>
</head>
<body>
<?php if (isset($error)): ?>
<div style="color: red;"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<form method="post">
<div>
<label>用户名:</label>
<input type="text" name="username" required>
</div>
<div>
<label>密码:</label>
<input type="password" name="password" required>
</div>
<div>
<label>
<input type="checkbox" name="remember_me"> 记住我
</label>
</div>
<div>
<input type="submit" name="login" value="登录">
</div>
</form>
</body>
</html>
购物车系统
购物车实现
<?php
session_start();
// 初始化购物车
if (!isset($_SESSION['cart'])) {
$_SESSION['cart'] = [];
}
// 添加商品到购物车
if (isset($_POST['add_to_cart'])) {
$product_id = intval($_POST['product_id']);
$quantity = intval($_POST['quantity']);
if ($quantity > 0) {
if (isset($_SESSION['cart'][$product_id])) {
$_SESSION['cart'][$product_id] += $quantity;
} else {
$_SESSION['cart'][$product_id] = $quantity;
}
}
}
// 更新购物车数量
if (isset($_POST['update_cart'])) {
foreach ($_POST['quantity'] as $product_id => $quantity) {
$product_id = intval($product_id);
$quantity = intval($quantity);
if ($quantity > 0) {
$_SESSION['cart'][$product_id] = $quantity;
} else {
unset($_SESSION['cart'][$product_id]);
}
}
}
// 从购物车移除商品
if (isset($_GET['remove'])) {
$product_id = intval($_GET['remove']);
unset($_SESSION['cart'][$product_id]);
}
// 清空购物车
if (isset($_POST['clear_cart'])) {
$_SESSION['cart'] = [];
}
// 获取购物车商品总数
function getCartItemCount() {
if (isset($_SESSION['cart'])) {
return array_sum($_SESSION['cart']);
}
return 0;
}
// 获取购物车总价
function getCartTotal() {
$total = 0;
if (isset($_SESSION['cart'])) {
foreach ($_SESSION['cart'] as $product_id => $quantity) {
$price = getProductPrice($product_id);
$total += $price * $quantity;
}
}
return $total;
}
function getProductPrice($product_id) {
// 实际应用中应该查询数据库获取价格
$prices = [
1 => 19.99,
2 => 29.99,
3 => 9.99
];
return isset($prices[$product_id]) ? $prices[$product_id] : 0;
}
function getProductName($product_id) {
// 实际应用中应该查询数据库获取商品名称
$names = [
1 => 'PHP教程',
2 => 'JavaScript教程',
3 => 'HTML教程'
];
return isset($names[$product_id]) ? $names[$product_id] : '未知商品';
}
?>
<!DOCTYPE html>
<html>
<head>
<title>购物车</title>
</head>
<body>
<h1>购物车 (<?php echo getCartItemCount(); ?> 件商品)</h1>
<?php if (empty($_SESSION['cart'])): ?>
<p>购物车为空</p>
<?php else: ?>
<form method="post">
<table border="1">
<tr>
<th>商品</th>
<th>单价</th>
<th>数量</th>
<th>小计</th>
<th>操作</th>
</tr>
<?php foreach ($_SESSION['cart'] as $product_id => $quantity): ?>
<tr>
<td><?php echo htmlspecialchars(getProductName($product_id)); ?></td>
<td>¥<?php echo number_format(getProductPrice($product_id), 2); ?></td>
<td>
<input type="number" name="quantity[<?php echo $product_id; ?>]"
value="<?php echo $quantity; ?>" min="1">
</td>
<td>¥<?php echo number_format(getProductPrice($product_id) * $quantity, 2); ?></td>
<td>
<a href="?remove=<?php echo $product_id; ?>">移除</a>
</td>
</tr>
<?php endforeach; ?>
<tr>
<td colspan="3">总计</td>
<td colspan="2">¥<?php echo number_format(getCartTotal(), 2); ?></td>
</tr>
</table>
<button type="submit" name="update_cart">更新购物车</button>
<button type="submit" name="clear_cart">清空购物车</button>
</form>
<?php endif; ?>
<h2>添加商品</h2>
<form method="post">
<select name="product_id">
<option value="1">PHP教程 - ¥19.99</option>
<option value="2">JavaScript教程 - ¥29.99</option>
<option value="3">HTML教程 - ¥9.99</option>
</select>
<input type="number" name="quantity" value="1" min="1">
<button type="submit" name="add_to_cart">添加到购物车</button>
</form>
</body>
</html>
Cookie和Session的安全考虑
常见安全威胁
- 会话劫持: 攻击者获取用户的Session ID
- 会话固定: 攻击者强制用户使用特定的Session ID
- 跨站脚本攻击(XSS): 通过XSS窃取Session Cookie
- 跨站请求伪造(CSRF): 利用用户的已认证状态执行非预期操作
安全最佳实践
Session安全配置
<?php
// Session安全配置
ini_set('session.use_strict_mode', 1); // 只接受服务器生成的Session ID
ini_set('session.use_only_cookies', 1); // 只使用Cookie存储Session ID
ini_set('session.cookie_httponly', 1); // 防止JavaScript访问Session Cookie
ini_set('session.cookie_secure', 1); // 仅通过HTTPS传输Session Cookie
ini_set('session.cookie_samesite', 'Strict'); // 防止CSRF攻击
ini_set('session.gc_maxlifetime', 1800); // Session过期时间(30分钟)
// 启动Session
session_start();
// 定期更换Session ID
if (!isset($_SESSION['created'])) {
$_SESSION['created'] = time();
} elseif (time() - $_SESSION['created'] > 300) {
// 每5分钟更换一次Session ID
session_regenerate_id(true);
$_SESSION['created'] = time();
}
// 验证用户代理(防止会话劫持)
if (isset($_SESSION['user_agent'])) {
if ($_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
// 用户代理不匹配,可能是会话劫持
session_unset();
session_destroy();
die("安全检测异常");
}
} else {
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
}
// 验证IP地址(可选,可能影响代理用户)
if (isset($_SESSION['ip_address'])) {
if ($_SESSION['ip_address'] !== $_SERVER['REMOTE_ADDR']) {
// IP地址不匹配
session_unset();
session_destroy();
die("安全检测异常");
}
} else {
$_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
}
?>
安全Cookie设置
安全Cookie设置
<?php
// 设置安全Cookie
setcookie(
"preferences", // Cookie名称
"dark_theme", // Cookie值
time() + (86400 * 30), // 过期时间(30天)
"/", // 路径
"example.com", // 域名
true, // 仅通过HTTPS传输
true // 仅HTTP访问,防止JavaScript访问
);
// 设置SameSite Cookie(防止CSRF)
header('Set-Cookie: preferences=dark_theme; expires=' . gmdate('D, d M Y H:i:s T', time() + 86400 * 30) . '; path=/; secure; httponly; SameSite=Strict');
?>
高级Session管理
分布式Session存储
在分布式系统中,Session数据需要共享存储,常见方案包括:
- 数据库存储: 将Session数据存储在关系型数据库中
- Redis存储: 使用Redis作为Session存储后端
- Memcached存储: 使用Memcached存储Session数据
Redis Session处理器
Redis Session处理器
<?php
class RedisSessionHandler implements SessionHandlerInterface
{
private $redis;
private $prefix = 'sess:';
private $ttl;
public function __construct($redis, $ttl = 1800) {
$this->redis = $redis;
$this->ttl = $ttl;
}
public function open($savePath, $sessionName) {
return true;
}
public function close() {
return true;
}
public function read($sessionId) {
$key = $this->prefix . $sessionId;
$data = $this->redis->get($key);
return $data ?: '';
}
public function write($sessionId, $sessionData) {
$key = $this->prefix . $sessionId;
return $this->redis->setex($key, $this->ttl, $sessionData);
}
public function destroy($sessionId) {
$key = $this->prefix . $sessionId;
return $this->redis->del($key) > 0;
}
public function gc($maxLifetime) {
// Redis会自动处理过期键,这里不需要额外操作
return true;
}
}
// 使用Redis Session处理器
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$handler = new RedisSessionHandler($redis, 1800);
session_set_save_handler($handler, true);
session_start();
?>
Session性能优化
- 避免在Session中存储大量数据
- 使用适当的Session垃圾回收设置
- 考虑使用内存存储(如Redis)提高性能
- 对Session数据进行压缩
- 使用Session锁避免并发问题
Cookie和Session的选择指南
| 场景 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端 | 服务器端 |
| 安全性 | 较低(用户可查看和修改) | 较高(数据存储在服务器) |
| 存储容量 | 较小(每个Cookie 4KB,每个域名有限制) | 较大(受服务器内存限制) |
| 持久性 | 可设置长期有效 | 通常随浏览器关闭而失效 |
| 适用场景 | 用户偏好设置、跟踪信息、记住登录状态 | 用户登录状态、购物车、敏感数据 |
| 性能影响 | 每次请求都会发送,增加带宽 | 服务器需要存储和检索数据 |
最佳实践总结
- 敏感数据使用Session存储
- 非敏感的用户偏好使用Cookie存储
- Session数据尽量精简
- 设置合理的Session过期时间
- 使用安全的Cookie标志(Secure, HttpOnly, SameSite)
- 定期更换Session ID
- 实施适当的Session安全措施