方案 1:基于 Session 的 Token 实现
核心原理
实现代码
<?php // 初始化Session session_start(); /** * 生成并存储Token */ function generateSessionToken() { $token = bin2hex(random_bytes(16)); $_SESSION['csrf_token'] = $token; $_SESSION['csrf_token_expire'] = time() + 3600; // 1小时有效期 return $token; } /** * 验证Session Token */ function verifySessionToken($token) { if (!isset($_SESSION['csrf_token']) || !isset($_SESSION['csrf_token_expire'])) { return false; } if (time() > $_SESSION['csrf_token_expire']) { unset($_SESSION['csrf_token'], $_SESSION['csrf_token_expire']); return false; } $isValid = hash_equals($_SESSION['csrf_token'], $token); if ($isValid) { unset($_SESSION['csrf_token'], $_SESSION['csrf_token_expire']); } return $isValid; } // 示例 $token = generateSessionToken(); echo '<input type="hidden" name="csrf_token" value="'.$token.'">'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $clientToken = $_POST['csrf_token'] ?? ''; if (verifySessionToken($clientToken)) { echo 'Token验证通过,执行业务逻辑'; } else { echo 'Token无效或已过期'; } } ?>优缺点
优点 | 缺点 |
实现简单,依赖 PHP 原生 Session | 依赖 Cookie,客户端禁用 Cookie 则失效 |
Token 存储在服务端,安全性高 | 分布式部署需配置 Session 共享,如 Redis |
支持服务端主动销毁 Token | 占用服务器内存(Session 默认存储在文件/内存) |
方案 2:基于 JWT(JSON Web Token)的 Token 实现
核心原理
前置条件
composer require firebase/php-jwt实现代码
<?php require 'vendor/autoload.php'; use Firebase\JWT\JWT; use Firebase\JWT\Key; $secretKey = 'your_strong_secret_key'; $algorithm = 'HS256'; /** * 生成JWT Token */ function generateJwtToken($payload = []) { global $secretKey, $algorithm; $defaultPayload = [ 'iat' => time(), 'exp' => time() + 3600, ]; $payload = array_merge($defaultPayload, $payload); return JWT::encode($payload, $secretKey, $algorithm); } /** * 验证JWT Token */ function verifyJwtToken($token) { global $secretKey, $algorithm; try { $decoded = JWT::decode($token, new Key($secretKey, $algorithm)); return (array)$decoded; } catch (Exception $e) { echo 'Token验证失败:' . $e->getMessage(); return false; } } // 示例 $userPayload = ['uid' => 1001, 'username' => 'test_user']; $jwtToken = generateJwtToken($userPayload); echo '生成的JWT Token:' . $jwtToken . '<br>'; $clientToken = $_GET['token'] ?? ''; $decodedData = verifyJwtToken($clientToken); if ($decodedData) { echo 'Token验证通过,用户ID:' . $decodedData['uid']; } else { echo 'Token无效'; } ?>优缺点
优点 | 缺点 |
无状态,分布式友好 | Token 无法主动销毁,仅能等过期(可用黑名单弥补) |
支持跨域、跨平台 | 负载信息 Base64 编码,不可存储敏感数据 |
传输体积小,可自定义数据 | 密钥泄露风险高,需严格保管 |
不依赖 Cookie,兼容性好 | 过期时间固定,需重新签发才能调整 |
方案 3:基于 Redis 的 Token 实现
核心原理
前置条件
实现代码
<?php function initRedis() { $redis = new Redis(); try { $redis->connect('127.0.0.1', 6379); // $redis->auth('your_redis_password'); return $redis; } catch (Exception $e) { echo 'Redis连接失败:' . $e->getMessage(); return null; } } function generateRedisToken($uid, $expire = 3600) { $redis = initRedis(); if (!$redis) return false; $token = md5($uid . time() . random_bytes(16)); $tokenKey = 'token:' . $token; $redis->setex($tokenKey, $expire, $uid); return $token; } function verifyRedisToken($token) { $redis = initRedis(); if (!$redis) return false; $tokenKey = 'token:' . $token; $uid = $redis->get($tokenKey); if ($uid) { $redis->expire($tokenKey, 3600); // 滑动有效期 return (int)$uid; } return false; } function destroyRedisToken($token) { $redis = initRedis(); if (!$redis) return false; $tokenKey = 'token:' . $token; return $redis->del($tokenKey) > 0; } // 示例 $token = generateRedisToken(1001); echo 'Redis Token:' . $token . '<br>'; $clientToken = $_POST['token'] ?? ''; $uid = verifyRedisToken($clientToken); if ($uid) { echo 'Token验证通过,用户ID:' . $uid; } else { echo 'Token无效或已过期'; } // destroyRedisToken($clientToken); ?>优缺点
优点 | 缺点 |
支持分布式、集群部署 | 依赖 Redis,增加系统复杂度 |
可主动销毁 Token,安全性高 | Redis 性能或容灾需关注 |
支持滑动有效期 | 需处理 Redis 连接异常,代码需容错 |
不依赖 Cookie,兼容性好 | 性能略低于 Session/JWT(需网络 IO) |
方案 4:基于数据库(MySQL)的 Token 实现
核心原理
数据库表结构
CREATE TABLE `user_token` ( `id` int(11) NOT NULL AUTO_INCREMENT, `uid` int(11) NOT NULL COMMENT '用户ID', `token` varchar(64) NOT NULL COMMENT '令牌值', `create_time` int(11) NOT NULL COMMENT '创建时间(时间戳)', `expire_time` int(11) NOT NULL COMMENT '过期时间(时间戳)', `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1-有效 0-无效', PRIMARY KEY (`id`), UNIQUE KEY `idx_token` (`token`), KEY `idx_uid` (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户令牌表';实现代码
<?php $dbConfig = [ 'host' => '127.0.0.1', 'user' => 'root', 'password' => 'your_db_password', 'dbname' => 'test', 'charset' => 'utf8mb4' ]; function initDb() { global $dbConfig; try { $dsn = "mysql:host={$dbConfig['host']};dbname={$dbConfig['dbname']};charset={$dbConfig['charset']}"; $pdo = new PDO($dsn, $dbConfig['user'], $dbConfig['password']); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); return $pdo; } catch (PDOException $e) { echo '数据库连接失败:' . $e->getMessage(); return null; } } function generateDbToken($uid, $expire = 3600) { $pdo = initDb(); if (!$pdo) return false; $token = hash('sha256', $uid . time() . random_bytes(32)); $createTime = time(); $expireTime = $createTime + $expire; $sql = "INSERT INTO user_token (uid, token, create_time, expire_time, status) VALUES (:uid, :token, :create_time, :expire_time, 1)"; $stmt = $pdo->prepare($sql); $stmt->bindParam(':uid', $uid, PDO::PARAM_INT); $stmt->bindParam(':token', $token, PDO::PARAM_STR); $stmt->bindParam(':create_time', $createTime, PDO::PARAM_INT); $stmt->bindParam(':expire_time', $expireTime, PDO::PARAM_INT); return $stmt->execute() ? $token : false; } function verifyDbToken($token) { $pdo = initDb(); if (!$pdo) return false; $now = time(); $sql = "SELECT uid FROM user_token WHERE token = :token AND status = 1 AND expire_time > :now LIMIT 1"; $stmt = $pdo->prepare($sql); $stmt->bindParam(':token', $token, PDO::PARAM_STR); $stmt->bindParam(':now', $now, PDO::PARAM_INT); $stmt->execute(); $result = $stmt->fetch(PDO::FETCH_ASSOC); if ($result) { return (int)$result['uid']; } return false; } function destroyDbToken($token) { $pdo = initDb(); if (!$pdo) return false; $sql = "UPDATE user_token SET status = 0 WHERE token = :token"; $stmt = $pdo->prepare($sql); $stmt->bindParam(':token', $token, PDO::PARAM_STR); return $stmt->execute() && $stmt->rowCount() > 0; } // 示例 $token = generateDbToken(1001); echo '数据库Token:' . $token . '<br>'; $clientToken = $_SERVER['HTTP_TOKEN'] ?? ''; $uid = verifyDbToken($clientToken); if ($uid) { echo 'Token验证通过,用户ID:' . $uid; } else { echo 'Token无效或已过期'; } // destroyDbToken($clientToken); ?>优缺点
优点 | 缺点 |
支持复杂 Token 管理(如批量禁用、查询记录) | 数据库 IO 性能低于 Redis/Session,高并发下易成瓶颈 |
数据持久化,方便追溯 | 需手动清理过期 Token,表数据易膨胀 |
可扩展字段,便于审计 | 分布式部署需考虑主从同步 |
支持主动销毁 Token | 代码复杂度高,需处理事务、异常等 |
各方案对比汇总表
对比维度 | Session Token | JWT Token | Redis Token | 数据库 Token |
存储位置 | 服务端(文件/内存) | 客户端 | 服务端(Redis) | 服务端(MySQL) |
状态特性 | 有状态 | 无状态 | 有状态 | 有状态 |
分布式支持 | 需 Session 共享 | 天然支持 | Redis 集群 | 主从同步/分库分表 |
主动销毁 | 支持 | 不支持(需黑名单) | 支持 | 支持 |
性能 | 高 | 极高 | 中 | 低 |
复杂度 | 低 | 中 | 中 | 高 |
适用场景 | 小型项目、CSRF 防护 | 前后端分离、无状态服务 | 中大型项目、分布式 | 审计、复杂权限、低并发 |
安全性 | 高 | 中 | 高 | 高 |
总结与选型建议
- 选型核心原则
- 小型单节点项目:优先选用 Session Token(实现简单)。
- 前后端分离/跨域场景:优先选用 JWT Token(无状态,易于扩展)。
- 中大型分布式项目:优先选用 Redis Token(性能与灵活性兼具)。
- 需审计/复杂管理场景:可选 数据库 Token(持久化与扩展性强)。
- 安全性建议
- 所有 Token 建议通过 HTTPS 传输,防止明文泄露。
- JWT 密钥需定期更换,且严密保管。
- Session/Redis/数据库 Token 建议设置合理有效期,验证后一次性销毁(CSRF 场景)或滑动刷新(登录态场景)。
- 混合使用场景
- 可结合 JWT + Redis,实现“无状态 + 可销毁”机制:JWT 存储基础信息,Redis 存储 JWT 黑名单,兼顾性能与灵活性。