PHP Session(会话)是在服务器端存储用户特定信息的机制,用于在用户浏览网站的不同页面时保持状态。与Cookie不同,Session数据存储在服务器上,只在客户端存储一个唯一的session_id。
理解Session和Cookie的区别对于选择正确的数据存储方式至关重要:
| 特性 | Session | Cookie |
|---|---|---|
| 存储位置 | 服务器端 | 客户端浏览器 |
| 存储容量 | 较大(受服务器内存限制) | 较小(每个Cookie约4KB) |
| 安全性 | 高(数据在服务器端) | 低(数据在客户端) |
| 生命周期 | 会话期间(可配置) | 可长期存储 |
| 数据传输 | 仅传输session_id | 每次请求都传输完整数据 |
| 性能影响 | 服务器内存占用 | 增加网络流量 |
| 适用场景 | 登录状态、敏感数据 | 用户偏好、跟踪信息 |
图:PHP Session工作流程
Session工作流程分为以下几个步骤:
session_start()<?php
// session_start()必须在任何HTML输出之前调用
session_start();
// 检查会话是否已启动
if (session_status() === PHP_SESSION_NONE) {
echo "会话尚未启动";
} elseif (session_status() === PHP_SESSION_ACTIVE) {
echo "会话已激活";
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Session示例</title>
</head>
<body>
<p>页面内容...</p>
</body>
</html>
<?php
// 在session_start()之前设置会话配置
ini_set('session.name', 'MYAPPSESSID'); // 自定义session名称
ini_set('session.cookie_lifetime', 86400); // Cookie有效期24小时
ini_set('session.gc_maxlifetime', 1440); // 会话数据保留24分钟
ini_set('session.cookie_secure', true); // 仅HTTPS传输
ini_set('session.cookie_httponly', true); // 防止JavaScript访问
ini_set('session.use_strict_mode', true); // 严格模式(防止会话固定攻击)
ini_set('session.cookie_samesite', 'Lax'); // SameSite属性
// 自定义会话存储路径(确保目录可写)
ini_set('session.save_path', '/var/www/sessions');
// 启动会话
session_start();
echo "会话已启动,使用自定义配置";
?>
<?php
/**
* 安全启动会话的函数
* @return bool 是否成功启动会话
*/
function safeSessionStart() {
// 检查会话是否已启动
if (session_status() === PHP_SESSION_ACTIVE) {
return true;
}
// 防止session fixation攻击
if (ini_get('session.use_strict_mode') === false) {
ini_set('session.use_strict_mode', true);
}
// 启动会话
if (!session_start()) {
error_log('无法启动会话');
return false;
}
// 验证会话ID
if (empty(session_id()) || !preg_match('/^[a-zA-Z0-9,-]{22,256}$/', session_id())) {
session_regenerate_id(true);
}
return true;
}
// 使用安全函数启动会话
if (safeSessionStart()) {
echo "会话安全启动成功";
echo "<br>Session ID: " . session_id();
} else {
echo "无法启动会话,请检查配置";
}
?>
<?php
session_start();
// 存储基本数据类型
$_SESSION['username'] = '张三';
$_SESSION['user_id'] = 12345;
$_SESSION['is_admin'] = true;
$_SESSION['last_login'] = time();
// 存储数组
$_SESSION['preferences'] = [
'theme' => 'dark',
'language' => 'zh-CN',
'notifications' => true
];
// 存储对象
class UserSettings {
public $timezone = 'Asia/Shanghai';
public $date_format = 'Y-m-d';
}
$_SESSION['settings'] = new UserSettings();
echo "Session变量已存储<br>";
// 读取Session变量
if (isset($_SESSION['username'])) {
echo "欢迎," . htmlspecialchars($_SESSION['username']) . "!<br>";
echo "用户ID: " . $_SESSION['user_id'] . "<br>";
echo "管理员: " . ($_SESSION['is_admin'] ? '是' : '否') . "<br>";
echo "最后登录: " . date('Y-m-d H:i:s', $_SESSION['last_login']) . "<br>";
// 读取数组
if (isset($_SESSION['preferences'])) {
echo "主题: " . $_SESSION['preferences']['theme'] . "<br>";
}
// 读取对象
if (isset($_SESSION['settings'])) {
echo "时区: " . $_SESSION['settings']->timezone . "<br>";
}
}
// 检查Session变量是否存在
if (!isset($_SESSION['visit_count'])) {
$_SESSION['visit_count'] = 1;
} else {
$_SESSION['visit_count']++;
}
echo "访问次数: " . $_SESSION['visit_count'];
?>
<?php
session_start();
/**
* 安全设置Session变量
* @param string $key 键名
* @param mixed $value 值(会被序列化存储)
* @param bool $overwrite 是否覆盖已存在的值
*/
function setSession($key, $value, $overwrite = true) {
if (!isset($_SESSION[$key]) || $overwrite) {
// 对字符串值进行安全过滤
if (is_string($value)) {
$_SESSION[$key] = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
} else {
$_SESSION[$key] = $value;
}
return true;
}
return false;
}
/**
* 安全获取Session变量
* @param string $key 键名
* @param mixed $default 默认值
* @return mixed 安全的值
*/
function getSession($key, $default = null) {
if (isset($_SESSION[$key])) {
$value = $_SESSION[$key];
// 如果是字符串且需要HTML输出,确保已转义
if (is_string($value)) {
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}
return $value;
}
return $default;
}
/**
* 安全删除Session变量
* @param string $key 键名
*/
function unsetSession($key) {
if (isset($_SESSION[$key])) {
unset($_SESSION[$key]);
return true;
}
return false;
}
// 使用示例
setSession('user_email', 'user@example.com');
setSession('user_data', [
'name' => '李四',
'age' => 25
]);
$email = getSession('user_email', '未设置邮箱');
$name = getSession('user_data')['name'] ?? '未知用户';
echo "邮箱: $email<br>";
echo "姓名: $name<br>";
// 删除特定Session变量
unsetSession('temp_data');
?>
<?php
session_start();
// 初始化购物车数组
if (!isset($_SESSION['cart'])) {
$_SESSION['cart'] = [];
}
// 添加商品到购物车
function addToCart($productId, $quantity = 1, $productName = '') {
if (!isset($_SESSION['cart'][$productId])) {
$_SESSION['cart'][$productId] = [
'quantity' => $quantity,
'name' => $productName,
'added_at' => time()
];
} else {
$_SESSION['cart'][$productId]['quantity'] += $quantity;
}
}
// 从购物车移除商品
function removeFromCart($productId) {
if (isset($_SESSION['cart'][$productId])) {
unset($_SESSION['cart'][$productId]);
return true;
}
return false;
}
// 获取购物车总商品数
function getCartTotal() {
if (!isset($_SESSION['cart'])) {
return 0;
}
$total = 0;
foreach ($_SESSION['cart'] as $item) {
$total += $item['quantity'];
}
return $total;
}
// 使用示例
addToCart(101, 2, 'PHP编程书籍');
addToCart(102, 1, 'JavaScript教程');
echo "购物车商品总数: " . getCartTotal() . "<br>";
// 显示购物车内容
if (isset($_SESSION['cart']) && !empty($_SESSION['cart'])) {
echo "<h4>购物车内容:</h4>";
echo "<ul>";
foreach ($_SESSION['cart'] as $productId => $item) {
echo "<li>{$item['name']} - 数量: {$item['quantity']}</li>";
}
echo "</ul>";
}
// 清空购物车
if (isset($_GET['clear_cart'])) {
unset($_SESSION['cart']);
echo "购物车已清空";
}
?>
<?php
session_start();
// 设置一些Session变量
$_SESSION['user'] = ['id' => 123, 'name' => '张三'];
$_SESSION['cart'] = ['item1', 'item2'];
$_SESSION['settings'] = ['theme' => 'dark'];
echo "销毁前的Session变量:<br>";
print_r($_SESSION);
// 使用unset()删除单个变量
unset($_SESSION['cart']);
// 使用unset()删除数组中的特定元素
if (isset($_SESSION['user']['name'])) {
unset($_SESSION['user']['name']);
}
echo "<br><br>销毁部分变量后的Session:<br>";
print_r($_SESSION);
// 批量删除多个变量
$varsToRemove = ['settings', 'temp_data'];
foreach ($varsToRemove as $var) {
if (isset($_SESSION[$var])) {
unset($_SESSION[$var]);
}
}
?>
<?php
/**
* 安全销毁会话的函数
* @param bool $destroyCookie 是否删除session cookie
* @param string $redirect 销毁后重定向的URL(可选)
*/
function destroySession($destroyCookie = true, $redirect = null) {
// 启动会话(如果尚未启动)
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// 清空所有Session变量
$_SESSION = [];
// 如果要删除session cookie
if ($destroyCookie) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
// 销毁会话
if (session_destroy()) {
// 如果需要重定向
if ($redirect) {
header('Location: ' . $redirect);
exit;
}
return true;
}
return false;
}
// 使用示例
session_start();
$_SESSION['username'] = 'test_user';
echo "当前Session ID: " . session_id() . "<br>";
echo "Session变量: ";
print_r($_SESSION);
// 销毁会话
if (destroySession(false)) {
echo "<br><br>会话已销毁<br>";
// 验证是否真的销毁了
if (session_status() === PHP_SESSION_NONE) {
echo "会话状态: 未激活";
}
} else {
echo "<br><br>销毁会话失败";
}
// 实际应用:用户注销
if (isset($_GET['logout'])) {
destroySession(true, 'login.php');
}
?>
<?php
/**
* 自动清理过期会话数据的类
*/
class SessionCleaner {
private $maxInactiveTime = 1800; // 30分钟(秒)
private $cleanupProbability = 1; // 1%的概率执行清理
public function __construct($maxInactiveTime = 1800) {
$this->maxInactiveTime = $maxInactiveTime;
}
/**
* 检查并清理过期会话
*/
public function checkAndClean() {
// 按概率执行清理(避免每次请求都清理)
if (rand(1, 100) <= $this->cleanupProbability) {
$this->cleanExpiredSessions();
}
// 检查当前会话是否过期
$this->checkCurrentSession();
}
/**
* 清理服务器上的过期会话文件
*/
private function cleanExpiredSessions() {
$sessionPath = session_save_path();
if (empty($sessionPath)) {
$sessionPath = sys_get_temp_dir();
}
$sessionPath = rtrim($sessionPath, '/') . '/';
$files = glob($sessionPath . 'sess_*');
foreach ($files as $file) {
if (filemtime($file) + $this->maxInactiveTime < time()) {
unlink($file);
}
}
}
/**
* 检查当前会话是否过期
*/
private function checkCurrentSession() {
session_start();
if (isset($_SESSION['LAST_ACTIVITY'])) {
// 检查会话是否过期
if (time() - $_SESSION['LAST_ACTIVITY'] > $this->maxInactiveTime) {
// 会话过期,销毁并重新生成ID
session_unset();
session_destroy();
session_start();
session_regenerate_id(true);
// 设置过期消息
$_SESSION['session_expired'] = true;
}
}
// 更新最后活动时间
$_SESSION['LAST_ACTIVITY'] = time();
}
/**
* 设置会话数据过期时间
* @param string $key 数据键名
* @param int $ttl 存活时间(秒)
*/
public function setWithTTL($key, $value, $ttl) {
$_SESSION[$key] = $value;
$_SESSION[$key . '_expiry'] = time() + $ttl;
}
/**
* 获取带TTL检查的会话数据
* @param string $key 数据键名
* @param mixed $default 默认值
*/
public function getWithTTL($key, $default = null) {
if (isset($_SESSION[$key])) {
$expiry = $_SESSION[$key . '_expiry'] ?? 0;
if ($expiry > time()) {
return $_SESSION[$key];
} else {
// 数据过期,清理
unset($_SESSION[$key]);
unset($_SESSION[$key . '_expiry']);
}
}
return $default;
}
}
// 使用示例
$cleaner = new SessionCleaner(1200); // 20分钟不活动则过期
// 每次页面加载时检查
$cleaner->checkAndClean();
// 设置带TTL的数据(10秒后过期)
$cleaner->setWithTTL('temp_data', '临时数据', 10);
// 获取数据(如果过期则返回null)
$tempData = $cleaner->getWithTTL('temp_data');
echo "临时数据: " . ($tempData ?: '已过期或不存在');
?>
| 配置项 | 推荐值 | 说明 |
|---|---|---|
session.name |
PHPSESSID |
Session Cookie的名称,可以修改为应用特定的名称 |
session.cookie_lifetime |
0 |
Session Cookie有效期,0表示浏览器关闭时过期 |
session.cookie_secure |
On |
仅在HTTPS连接时传输Session Cookie |
session.cookie_httponly |
On |
防止JavaScript访问Session Cookie |
session.cookie_samesite |
Lax 或 Strict |
防止CSRF攻击,控制跨站请求是否发送Cookie |
session.use_strict_mode |
On |
只接受服务器生成的session_id,防止会话固定攻击 |
session.use_only_cookies |
On |
只使用Cookie传递session_id,防止URL传递的安全问题 |
session.gc_maxlifetime |
1440 (24分钟) |
Session数据在服务器上的最大存活时间(秒) |
session.gc_probability |
1 |
垃圾回收概率(分子) |
session.gc_divisor |
100 |
垃圾回收概率(分母),概率 = gc_probability/gc_divisor |
session.hash_function |
sha256 |
生成session_id的哈希算法 |
session.sid_length |
32 |
session_id的长度(字符) |
session.sid_bits_per_character |
6 |
每个字符的位数,4/5/6对应16/32/64进制 |
<?php
/**
* 安全的Session启动类,防止会话固定攻击
*/
class SecureSession {
public static function start() {
// 确保使用严格模式
ini_set('session.use_strict_mode', true);
// 只使用Cookie传递session_id
ini_set('session.use_only_cookies', true);
// 启动会话
session_start();
// 如果会话是新创建的,标记为需要重新生成ID
if (!isset($_SESSION['created'])) {
self::regenerate();
}
// 检查会话是否过期(30分钟)
if (isset($_SESSION['created']) && (time() - $_SESSION['created'] > 1800)) {
self::regenerate();
}
// 检查用户代理是否变化(防止会话劫持)
self::checkUserAgent();
}
/**
* 重新生成session_id
*/
public static function regenerate() {
// 保存旧会话数据
$oldSessionData = $_SESSION;
// 销毁旧会话
session_regenerate_id(true);
// 恢复会话数据
$_SESSION = $oldSessionData;
// 设置创建时间
$_SESSION['created'] = time();
}
/**
* 检查用户代理是否一致
*/
private static function checkUserAgent() {
$currentAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (!isset($_SESSION['user_agent'])) {
// 第一次设置用户代理
$_SESSION['user_agent'] = hash('sha256', $currentAgent);
} else {
// 检查用户代理是否变化
if ($_SESSION['user_agent'] !== hash('sha256', $currentAgent)) {
// 用户代理变化,可能是会话劫持
self::destroy();
throw new Exception('检测到可疑的会话活动');
}
}
}
/**
* 安全销毁会话
*/
public static function destroy() {
// 清空所有Session变量
$_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();
}
/**
* 获取安全的session_id
*/
public static function getSessionId() {
return session_id();
}
}
// 使用示例
try {
SecureSession::start();
// 设置用户数据
$_SESSION['user_id'] = 123;
$_SESSION['username'] = '安全用户';
echo "安全会话已启动<br>";
echo "Session ID: " . SecureSession::getSessionId() . "<br>";
echo "会话创建时间: " . date('Y-m-d H:i:s', $_SESSION['created']) . "<br>";
} catch (Exception $e) {
echo "会话错误: " . $e->getMessage();
}
?>
<?php
/**
* 高级会话安全类,防止多种攻击
*/
class AdvancedSessionSecurity {
private static $fingerprintKeys = [
'HTTP_USER_AGENT',
'HTTP_ACCEPT_LANGUAGE',
'REMOTE_ADDR'
];
/**
* 启动安全会话
*/
public static function startSecureSession() {
// 基本安全配置
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => $_SERVER['HTTP_HOST'],
'secure' => isset($_SERVER['HTTPS']),
'httponly' => true,
'samesite' => 'Strict'
]);
// 启动会话
session_start();
// 如果是新会话,初始化安全数据
if (empty($_SESSION)) {
self::initializeSecurityData();
} else {
// 验证会话安全性
if (!self::validateSession()) {
// 会话不安全,销毁并重新生成
session_regenerate_id(true);
self::initializeSecurityData();
}
}
// 定期重新生成session_id(每10个请求)
if (!isset($_SESSION['request_count'])) {
$_SESSION['request_count'] = 1;
} else {
$_SESSION['request_count']++;
if ($_SESSION['request_count'] >= 10) {
session_regenerate_id(true);
$_SESSION['request_count'] = 1;
}
}
}
/**
* 初始化安全数据
*/
private static function initializeSecurityData() {
// 生成会话指纹
$_SESSION['fingerprint'] = self::generateFingerprint();
// 设置会话创建时间和IP
$_SESSION['created_at'] = time();
$_SESSION['ip_address'] = self::getClientIp();
// 设置请求计数
$_SESSION['request_count'] = 1;
}
/**
* 验证会话安全性
*/
private static function validateSession() {
// 检查会话是否过期(1小时)
if (time() - $_SESSION['created_at'] > 3600) {
return false;
}
// 检查IP地址是否变化(允许同一网段的变化)
$currentIp = self::getClientIp();
$sessionIp = $_SESSION['ip_address'];
if (!self::ipsInSameSubnet($currentIp, $sessionIp)) {
return false;
}
// 检查指纹是否匹配
if ($_SESSION['fingerprint'] !== self::generateFingerprint()) {
return false;
}
return true;
}
/**
* 生成会话指纹
*/
private static function generateFingerprint() {
$data = '';
foreach (self::$fingerprintKeys as $key) {
if (isset($_SERVER[$key])) {
$data .= $_SERVER[$key];
}
}
return hash('sha256', $data);
}
/**
* 获取客户端IP地址(考虑代理)
*/
private static function getClientIp() {
$ipKeys = [
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR'
];
foreach ($ipKeys as $key) {
if (isset($_SERVER[$key])) {
$ipList = explode(',', $_SERVER[$key]);
foreach ($ipList as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
/**
* 检查两个IP是否在同一网段
*/
private static function ipsInSameSubnet($ip1, $ip2) {
// 简化检查:只比较前三个八位组(/24子网)
$parts1 = explode('.', $ip1);
$parts2 = explode('.', $ip2);
if (count($parts1) !== 4 || count($parts2) !== 4) {
return false;
}
return $parts1[0] === $parts2[0]
&& $parts1[1] === $parts2[1]
&& $parts1[2] === $parts2[2];
}
}
// 使用示例
AdvancedSessionSecurity::startSecureSession();
// 设置用户数据
$_SESSION['user'] = [
'id' => 456,
'name' => '高级用户',
'email' => 'user@example.com'
];
echo "高级安全会话已启动<br>";
echo "会话指纹: " . substr($_SESSION['fingerprint'], 0, 16) . "...<br>";
echo "会话创建于: " . date('Y-m-d H:i:s', $_SESSION['created_at']) . "<br>";
echo "原始IP: " . $_SESSION['ip_address'] . "<br>";
echo "请求计数: " . $_SESSION['request_count'];
?>
<?php
// login.php - 用户登录页面
class UserAuth {
private $db;
public function __construct($dbConnection) {
$this->db = $dbConnection;
$this->initSession();
}
/**
* 初始化会话安全设置
*/
private function initSession() {
// 会话安全配置
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => $_SERVER['HTTP_HOST'],
'secure' => isset($_SERVER['HTTPS']),
'httponly' => true,
'samesite' => 'Lax'
]);
ini_set('session.use_strict_mode', true);
ini_set('session.use_only_cookies', true);
session_start();
// 如果是新会话,初始化
if (empty($_SESSION)) {
session_regenerate_id(true);
$_SESSION['created'] = time();
$_SESSION['ip'] = $this->getClientIp();
$_SESSION['user_agent_hash'] = hash('sha256', $_SERVER['HTTP_USER_AGENT'] ?? '');
}
// 定期重新生成session_id
if (isset($_SESSION['created']) && (time() - $_SESSION['created'] > 300)) {
session_regenerate_id(true);
$_SESSION['created'] = time();
}
}
/**
* 用户登录
*/
public function login($username, $password, $remember = false) {
// 验证用户名和密码(这里应该查询数据库)
$user = $this->validateCredentials($username, $password);
if ($user) {
// 设置会话变量
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['email'] = $user['email'];
$_SESSION['role'] = $user['role'];
$_SESSION['last_login'] = time();
$_SESSION['login_count'] = ($user['login_count'] ?? 0) + 1;
// 更新登录时间
$this->updateLastLogin($user['id']);
// 如果选择记住我,设置长期Cookie
if ($remember) {
$this->setRememberMeCookie($user['id']);
}
// 记录登录日志
$this->logLogin($user['id'], '成功');
return true;
}
// 记录失败的登录尝试
$this->logLoginAttempt($username, '失败');
return false;
}
/**
* 用户注销
*/
public function logout() {
// 删除记住我Cookie
$this->clearRememberMeCookie();
// 记录注销日志
if (isset($_SESSION['user_id'])) {
$this->logLogout($_SESSION['user_id']);
}
// 销毁会话
session_unset();
session_destroy();
// 删除会话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']
);
}
}
/**
* 检查用户是否已登录
*/
public function isLoggedIn() {
// 检查会话中的用户ID
if (isset($_SESSION['user_id'])) {
// 验证会话安全性
if ($this->validateSession()) {
return true;
}
}
// 检查记住我Cookie
return $this->checkRememberMeCookie();
}
/**
* 获取当前用户信息
*/
public function getCurrentUser() {
if ($this->isLoggedIn()) {
return [
'id' => $_SESSION['user_id'],
'username' => $_SESSION['username'],
'email' => $_SESSION['email'],
'role' => $_SESSION['role']
];
}
return null;
}
/**
* 验证会话安全性
*/
private function validateSession() {
// 检查IP地址(允许相同子网)
$currentIp = $this->getClientIp();
$sessionIp = $_SESSION['ip'] ?? '';
if (!$this->ipsInSameSubnet($currentIp, $sessionIp)) {
$this->logout();
return false;
}
// 检查用户代理
$currentAgentHash = hash('sha256', $_SERVER['HTTP_USER_AGENT'] ?? '');
$sessionAgentHash = $_SESSION['user_agent_hash'] ?? '';
if ($currentAgentHash !== $sessionAgentHash) {
$this->logout();
return false;
}
// 检查会话是否过期(30分钟不活动)
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > 1800)) {
$this->logout();
return false;
}
// 更新最后活动时间
$_SESSION['last_activity'] = time();
return true;
}
// 其他辅助方法...
private function validateCredentials($username, $password) {
// 这里应该查询数据库验证用户
// 示例:返回模拟用户数据
$users = [
'admin' => [
'id' => 1,
'username' => 'admin',
'email' => 'admin@example.com',
'role' => 'admin',
'password_hash' => password_hash('admin123', PASSWORD_DEFAULT)
]
];
if (isset($users[$username]) && password_verify($password, $users[$username]['password_hash'])) {
return $users[$username];
}
return false;
}
private function getClientIp() {
// 简化版本,实际应该考虑代理
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
private function ipsInSameSubnet($ip1, $ip2) {
// 简化检查
return substr($ip1, 0, strrpos($ip1, '.')) === substr($ip2, 0, strrpos($ip2, '.'));
}
}
// 使用示例
$auth = new UserAuth(null); // 实际应该传入数据库连接
// 处理登录表单
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember_me']);
if ($auth->login($username, $password, $remember)) {
header('Location: dashboard.php');
exit;
} else {
$error = '用户名或密码错误';
}
}
// 处理注销
if (isset($_GET['logout'])) {
$auth->logout();
header('Location: login.php');
exit;
}
// 检查当前登录状态
if ($auth->isLoggedIn()) {
$user = $auth->getCurrentUser();
echo "欢迎,{$user['username']}!<a href='?logout=1'>注销</a>";
} else {
// 显示登录表单
?>
<form method="POST">
<h2>用户登录</h2>
<?php if (isset($error)) echo "<p style='color:red;'>$error</p>"; ?>
<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>
<button type="submit" name="login">登录</button>
</form>
<?php
}
?>
<?php
// cart.php - 购物车系统
session_start();
class ShoppingCart {
private $cartKey = 'shopping_cart';
public function __construct() {
// 初始化购物车
if (!isset($_SESSION[$this->cartKey])) {
$_SESSION[$this->cartKey] = [];
}
}
// 添加商品
public function addItem($productId, $quantity = 1, $productData = []) {
$cart = $this->getCart();
if (isset($cart[$productId])) {
$cart[$productId]['quantity'] += $quantity;
} else {
$cart[$productId] = [
'quantity' => $quantity,
'added_at' => time(),
'data' => array_merge([
'name' => '商品' . $productId,
'price' => 0
], $productData)
];
}
$this->saveCart($cart);
return $cart[$productId]['quantity'];
}
// 更新商品数量
public function updateQuantity($productId, $quantity) {
if ($quantity <= 0) {
return $this->removeItem($productId);
}
$cart = $this->getCart();
if (isset($cart[$productId])) {
$cart[$productId]['quantity'] = $quantity;
$cart[$productId]['updated_at'] = time();
$this->saveCart($cart);
return true;
}
return false;
}
// 移除商品
public function removeItem($productId) {
$cart = $this->getCart();
if (isset($cart[$productId])) {
unset($cart[$productId]);
$this->saveCart($cart);
return true;
}
return false;
}
// 获取购物车内容
public function getCart() {
return $_SESSION[$this->cartKey] ?? [];
}
// 获取商品总数
public function getTotalItems() {
$cart = $this->getCart();
$total = 0;
foreach ($cart as $item) {
$total += $item['quantity'];
}
return $total;
}
// 计算总价
public function getTotalPrice() {
$cart = $this->getCart();
$total = 0;
foreach ($cart as $item) {
$price = $item['data']['price'] ?? 0;
$total += $item['quantity'] * $price;
}
return $total;
}
// 清空购物车
public function clearCart() {
unset($_SESSION[$this->cartKey]);
return true;
}
// 获取购物车摘要
public function getSummary() {
$cart = $this->getCart();
$summary = [];
foreach ($cart as $productId => $item) {
$summary[] = [
'id' => $productId,
'name' => $item['data']['name'],
'quantity' => $item['quantity'],
'price' => $item['data']['price'],
'subtotal' => $item['quantity'] * $item['data']['price']
];
}
return $summary;
}
// 保存购物车
private function saveCart($cart) {
$_SESSION[$this->cartKey] = $cart;
}
}
// 使用示例
$cart = new ShoppingCart();
// 处理添加商品请求
if (isset($_GET['add_to_cart'])) {
$productId = $_GET['add_to_cart'];
$quantity = $_GET['quantity'] ?? 1;
$newQuantity = $cart->addItem($productId, $quantity, [
'name' => '示例商品 ' . $productId,
'price' => 100 * $productId
]);
echo "商品已添加到购物车,当前数量: $newQuantity";
}
// 显示购物车
echo "<h2>购物车 (共" . $cart->getTotalItems() . "件商品)</h2>";
$cartItems = $cart->getCart();
if (empty($cartItems)) {
echo "<p>购物车为空</p>";
} else {
echo "<table border='1' cellpadding='8' style='width:100%;'>";
echo "<thead><tr><th>商品</th><th>单价</th><th>数量</th><th>小计</th><th>操作</th></tr></thead>";
echo "<tbody>";
foreach ($cartItems as $productId => $item) {
$name = $item['data']['name'];
$price = $item['data']['price'];
$quantity = $item['quantity'];
$subtotal = $price * $quantity;
echo "<tr>";
echo "<td>$name</td>";
echo "<td>¥$price</td>";
echo "<td>$quantity</td>";
echo "<td>¥$subtotal</td>";
echo "<td><a href='?remove=$productId'>移除</a></td>";
echo "</tr>";
}
$totalPrice = $cart->getTotalPrice();
echo "<tr><td colspan='3'><strong>总计</strong></td><td colspan='2'><strong>¥$totalPrice</strong></td></tr>";
echo "</tbody></table>";
echo "<p><a href='?clear=1'>清空购物车</a></p>";
}
// 处理移除请求
if (isset($_GET['remove'])) {
$cart->removeItem($_GET['remove']);
header('Location: cart.php');
exit;
}
// 处理清空请求
if (isset($_GET['clear'])) {
$cart->clearCart();
header('Location: cart.php');
exit;
}
?>
<?php
// wizard.php - 多步骤表单向导
session_start();
class FormWizard {
private $steps = [];
private $currentStep = 1;
private $sessionKey = 'form_wizard';
public function __construct($steps) {
$this->steps = $steps;
// 初始化向导数据
if (!isset($_SESSION[$this->sessionKey])) {
$_SESSION[$this->sessionKey] = [
'current_step' => 1,
'data' => [],
'completed_steps' => []
];
}
$this->currentStep = $_SESSION[$this->sessionKey]['current_step'];
}
// 处理表单提交
public function processStep($step, $data) {
// 验证步骤编号
if ($step < 1 || $step > count($this->steps)) {
return false;
}
// 保存数据
$_SESSION[$this->sessionKey]['data'][$step] = $data;
// 标记步骤完成
if (!in_array($step, $_SESSION[$this->sessionKey]['completed_steps'])) {
$_SESSION[$this->sessionKey]['completed_steps'][] = $step;
}
// 更新当前步骤
if ($step < count($this->steps)) {
$_SESSION[$this->sessionKey]['current_step'] = $step + 1;
$this->currentStep = $step + 1;
} else {
// 最后一步完成
$_SESSION[$this->sessionKey]['current_step'] = 'completed';
$this->currentStep = 'completed';
}
return true;
}
// 获取当前步骤
public function getCurrentStep() {
return $this->currentStep;
}
// 获取步骤数据
public function getStepData($step) {
return $_SESSION[$this->sessionKey]['data'][$step] ?? [];
}
// 获取所有数据
public function getAllData() {
return $_SESSION[$this->sessionKey]['data'] ?? [];
}
// 重置向导
public function reset() {
unset($_SESSION[$this->sessionKey]);
}
// 获取进度百分比
public function getProgress() {
if ($this->currentStep === 'completed') {
return 100;
}
$totalSteps = count($this->steps);
return round(($this->currentStep - 1) / $totalSteps * 100);
}
// 渲染当前步骤
public function renderCurrentStep() {
if ($this->currentStep === 'completed') {
return $this->renderCompletion();
}
$stepIndex = $this->currentStep - 1;
$stepConfig = $this->steps[$stepIndex];
$data = $this->getStepData($this->currentStep);
ob_start();
?>
<div class="wizard-step">
<h3>步骤 <?= $this->currentStep ?>: <?= $stepConfig['title'] ?></h3>
<div class="progress" style="height: 10px; margin: 20px 0;">
<div class="progress-bar" style="width: <?= $this->getProgress() ?>%;"></div>
</div>
<form method="POST">
<input type="hidden" name="step" value="<?= $this->currentStep ?>">
<?php
// 渲染表单字段
foreach ($stepConfig['fields'] as $fieldName => $fieldConfig) {
$value = $data[$fieldName] ?? '';
switch ($fieldConfig['type']) {
case 'text':
echo "<div><label>{$fieldConfig['label']}:</label>";
echo "<input type='text' name='{$fieldName}' value='{$value}' required></div>";
break;
case 'select':
echo "<div><label>{$fieldConfig['label']}:</label>";
echo "<select name='{$fieldName}'>";
foreach ($fieldConfig['options'] as $optionValue => $optionLabel) {
$selected = ($value == $optionValue) ? 'selected' : '';
echo "<option value='{$optionValue}' {$selected}>{$optionLabel}</option>";
}
echo "</select></div>";
break;
// 其他字段类型...
}
}
?>
<div style="margin-top: 20px;">
<?php if ($this->currentStep > 1): ?>
<button type="submit" name="action" value="prev">上一步</button>
<?php endif; ?>
<button type="submit" name="action" value="next">
<?= ($this->currentStep == count($this->steps)) ? '完成' : '下一步' ?>
</button>
</div>
</form>
</div>
<?php
return ob_get_clean();
}
// 渲染完成页面
private function renderCompletion() {
$allData = $this->getAllData();
ob_start();
?>
<div class="wizard-complete">
<h3>完成!</h3>
<p>您已成功完成所有步骤。以下是您填写的信息:</p>
<table border="1" cellpadding="10" style="width:100%;">
<?php foreach ($allData as $step => $data): ?>
<?php foreach ($data as $key => $value): ?>
<tr>
<th><?= $key ?></th>
<td><?= htmlspecialchars($value) ?></td>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
</table>
<p style="margin-top: 20px;">
<a href="?reset=1">重新开始</a>
</p>
</div>
<?php
return ob_get_clean();
}
}
// 定义向导步骤
$wizardSteps = [
[
'title' => '个人信息',
'fields' => [
'name' => ['type' => 'text', 'label' => '姓名'],
'email' => ['type' => 'text', 'label' => '邮箱']
]
],
[
'title' => '地址信息',
'fields' => [
'address' => ['type' => 'text', 'label' => '地址'],
'city' => ['type' => 'text', 'label' => '城市']
]
],
[
'title' => '偏好设置',
'fields' => [
'newsletter' => [
'type' => 'select',
'label' => '订阅新闻',
'options' => ['0' => '不订阅', '1' => '订阅']
]
]
]
];
// 创建向导实例
$wizard = new FormWizard($wizardSteps);
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$step = $_POST['step'] ?? 1;
$action = $_POST['action'] ?? 'next';
if ($action === 'next') {
// 处理当前步骤数据
unset($_POST['step'], $_POST['action']);
$wizard->processStep($step, $_POST);
} elseif ($action === 'prev' && $step > 1) {
// 返回上一步
$_SESSION[$wizard->getSessionKey()]['current_step'] = $step - 1;
}
}
// 处理重置请求
if (isset($_GET['reset'])) {
$wizard->reset();
header('Location: wizard.php');
exit;
}
// 渲染页面
echo "<h2>多步骤表单向导</h2>";
echo $wizard->renderCurrentStep();
?>
常见原因和解决方案:
session_start()之前没有输出任何内容<?php
// 错误的写法(HTML输出在前)
echo "一些输出";
session_start(); // 这里会出错
// 正确的写法
session_start();
echo "一些输出";
?>
解决方案:
session_start()在任何输出之前调用ob_start()生命周期对比:
| 存储方式 | 客户端存储时间 | 服务器端存储时间 | 如何延长 |
|---|---|---|---|
| Session Cookie | 浏览器会话期间 | session.gc_maxlifetime(默认24分钟) | 更新最后活动时间 |
| 持久Cookie | 设置的时间(如30天) | 不适用 | 重新设置过期时间 |
| Session数据 | 不适用 | session.gc_maxlifetime | 更新会话文件时间戳 |
高并发环境下的Session优化:
session_set_save_handler()<?php
/**
* 数据库Session处理器
*/
class DatabaseSessionHandler implements SessionHandlerInterface {
private $db;
private $tableName = 'sessions';
public function __construct($dbConnection) {
$this->db = $dbConnection;
}
public function open($savePath, $sessionName) {
return true;
}
public function close() {
return true;
}
public function read($sessionId) {
$stmt = $this->db->prepare("SELECT data FROM {$this->tableName} WHERE id = ? AND expiry > ?");
$stmt->execute([$sessionId, time()]);
if ($row = $stmt->fetch()) {
return $row['data'];
}
return '';
}
public function write($sessionId, $sessionData) {
$expiry = time() + (int)ini_get('session.gc_maxlifetime');
$stmt = $this->db->prepare("
INSERT INTO {$this->tableName} (id, data, expiry, created_at)
VALUES (?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE
data = VALUES(data),
expiry = VALUES(expiry)
");
return $stmt->execute([$sessionId, $sessionData, $expiry]);
}
public function destroy($sessionId) {
$stmt = $this->db->prepare("DELETE FROM {$this->tableName} WHERE id = ?");
return $stmt->execute([$sessionId]);
}
public function gc($maxLifetime) {
$stmt = $this->db->prepare("DELETE FROM {$this->tableName} WHERE expiry < ?");
return $stmt->execute([time() - $maxLifetime]);
}
}
// 使用自定义处理器
$db = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$handler = new DatabaseSessionHandler($db);
session_set_save_handler($handler, true);
session_start();
// 现在Session将存储在数据库中
$_SESSION['test'] = '数据库存储的Session数据';
echo "Session数据已存储到数据库";
?>