本文目录导读:

我来为您详细介绍 PHP 中实现时效令牌的几种方法。
基于时间戳的令牌生成
基础实现
class TimeBasedToken {
/**
* 生成时效令牌
* @param string $data 要加密的数据
* @param int $expireTime 过期时间(秒)
* @return string
*/
public static function generate($data, $expireTime = 3600) {
$expireTimestamp = time() + $expireTime;
$payload = json_encode([
'data' => $data,
'exp' => $expireTimestamp,
'iat' => time()
]);
return base64_encode($payload) . '.' .
self::sign($payload);
}
/**
* 验证令牌
* @param string $token
* @return array|false
*/
public static function verify($token) {
$parts = explode('.', $token);
if (count($parts) !== 2) {
return false;
}
$payload = base64_decode($parts[0]);
$signature = $parts[1];
// 验证签名
if (!self::verifySignature($payload, $signature)) {
return false;
}
$data = json_decode($payload, true);
// 检查是否过期
if ($data['exp'] < time()) {
return false;
}
return $data;
}
private static function sign($payload) {
$secret = 'your-secret-key'; // 使用环境变量存储
return hash_hmac('sha256', $payload, $secret);
}
private static function verifySignature($payload, $signature) {
return hash_equals(self::sign($payload), $signature);
}
}
// 使用示例
$token = TimeBasedToken::generate('user_data', 3600); // 1小时过期
$result = TimeBasedToken::verify($token);
使用 JWT(JSON Web Token)
推荐使用 firebase/php-jwt 库:
// 安装:composer require firebase/php-jwt
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtToken {
private static $key = 'your-secret-key'; // 应该从环境变量获取
/**
* 生成JWT令牌
*/
public static function generate($userId, $expireHours = 24) {
$payload = [
'iss' => 'your-app', // 签发者
'iat' => time(), // 签发时间
'exp' => time() + ($expireHours * 3600), // 过期时间
'user_id' => $userId, // 自定义数据
'jti' => uniqid('token_') // 令牌ID,用于吊销
];
return JWT::encode($payload, self::$key, 'HS256');
}
/**
* 验证JWT令牌
*/
public static function verify($token) {
try {
$decoded = JWT::decode($token, new Key(self::$key, 'HS256'));
return (array) $decoded;
} catch (\Exception $e) {
// 令牌过期或无效
return false;
}
}
}
// 使用示例
$token = JwtToken::generate(123, 48); // 48小时过期
$userData = JwtToken::verify($token);
基于 Redis 的令牌管理
class RedisTokenManager {
private $redis;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
/**
* 生成并存储令牌
*/
public function generateToken($userId, $expireSeconds = 3600) {
$token = bin2hex(random_bytes(32)); // 生成随机令牌
$tokenData = [
'user_id' => $userId,
'created_at' => time(),
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
];
// 存储到Redis,设置过期时间
$this->redis->setex(
"token:{$token}",
$expireSeconds,
json_encode($tokenData)
);
return $token;
}
/**
* 验证令牌
*/
public function verifyToken($token) {
$tokenKey = "token:{$token}";
if (!$this->redis->exists($tokenKey)) {
return false;
}
$tokenData = json_decode($this->redis->get($tokenKey), true);
// 可选:续期令牌
$this->redis->expire($tokenKey, 3600); // 续期1小时
return $tokenData;
}
/**
* 吊销令牌
*/
public function revokeToken($token) {
$this->redis->del("token:{$token}");
}
/**
* 清理过期令牌
*/
public function cleanExpiredTokens() {
// Redis自动处理过期键,无需手动清理
}
}
带刷新机制的完整实现
class TokenAuth {
private $secretKey;
private $refreshExpire = 30 * 24 * 3600; // 30天
private $accessExpire = 3600; // 1小时
public function __construct($secretKey) {
$this->secretKey = $secretKey;
}
/**
* 生成访问令牌和刷新令牌
*/
public function generateTokens($userId) {
$accessToken = $this->generateAccessToken($userId);
$refreshToken = $this->generateRefreshToken();
return [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_in' => $this->accessExpire
];
}
/**
* 刷新访问令牌
*/
public function refreshAccessToken($refreshToken) {
// 验证刷新令牌
$refreshData = $this->verifyRefreshToken($refreshToken);
if (!$refreshData) {
return false;
}
// 生成新的访问令牌
$newAccessToken = $this->generateAccessToken($refreshData['user_id']);
return [
'access_token' => $newAccessToken,
'expires_in' => $this->accessExpire
];
}
private function generateAccessToken($userId) {
$header = base64_encode(json_encode(['typ' => 'JWT', 'alg' => 'HS256']));
$payload = base64_encode(json_encode([
'user_id' => $userId,
'exp' => time() + $this->accessExpire,
'iat' => time(),
'type' => 'access'
]));
$signature = hash_hmac('sha256', "$header.$payload", $this->secretKey);
return "$header.$payload.$signature";
}
private function generateRefreshToken() {
return bin2hex(random_bytes(32));
}
private function verifyRefreshToken($refreshToken) {
// 从数据库或Redis验证刷新令牌
// 这里需要实现数据库查询逻辑
return ['user_id' => 123]; // 示例返回值
}
}
实际应用示例
// 用户登录
class AuthController {
private $tokenAuth;
private $db;
public function login($username, $password) {
// 验证用户凭据
$user = $this->db->query("SELECT * FROM users WHERE username = ?", [$username]);
if (!password_verify($password, $user['password'])) {
return ['error' => '无效的凭据'];
}
// 生成令牌
$tokens = $this->tokenAuth->generateTokens($user['id']);
// 存储刷新令牌到数据库
$this->db->query(
"INSERT INTO refresh_tokens (user_id, token, expires_at) VALUES (?, ?, ?)",
[$user['id'], $tokens['refresh_token'], date('Y-m-d H:i:s', time() + 30*24*3600)]
);
return [
'success' => true,
'tokens' => $tokens
];
}
// 验证请求中的访问令牌
public function authenticate() {
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (strpos($authHeader, 'Bearer ') !== 0) {
http_response_code(401);
echo json_encode(['error' => '未授权']);
exit;
}
$token = substr($authHeader, 7);
$userData = $this->tokenAuth->verifyAccessToken($token);
if (!$userData) {
http_response_code(401);
echo json_encode(['error' => '令牌过期或无效']);
exit;
}
return $userData;
}
}
安全建议
- 密钥管理:永远不要在代码中硬编码密钥,使用环境变量
- HTTPS:始终使用HTTPS传输令牌
- 令牌存储:前端使用HttpOnly Cookie或localStorage
- 验证所有请求:在中间件中统一验证令牌
- 限制令牌范围:实现基于角色的访问控制
// 示例:中间件验证
class AuthMiddleware {
public function handle($request, $next) {
$token = $request->bearerToken();
try {
$payload = JWT::decode($token, $this->key, ['HS256']);
// 将用户信息存储到请求中
$request->setUser($payload->user_id);
$request->setRole($payload->role);
return $next($request);
} catch (ExpiredException $e) {
return response()->json(['error' => '令牌已过期'], 401);
} catch (\Exception $e) {
return response()->json(['error' => '无效令牌'], 401);
}
}
}
选择合适的实现方式取决于您的具体需求:
- 简单需求:使用基于时间戳的简单令牌
- 标准API:使用JWT
- 高安全性:结合JWT和Redis存储
- 需要刷新机制:实现访问令牌+刷新令牌模式