本文目录导读:

在 PHP 中实现“短时凭证”(通常指短期有效的访问令牌或临时授权凭证),常见有以下几种方案:
JWT(JSON Web Token)- 最常用
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class ShortLivedCredential {
private $secretKey;
private $expiryTime;
public function __construct($secretKey, $expiryTime = 3600) {
$this->secretKey = $secretKey;
$this->expiryTime = $expiryTime; // 默认1小时
}
// 生成短时凭证
public function generateToken($userId, $permissions = []) {
$issuedAt = time();
$expiration = $issuedAt + $this->expiryTime;
$payload = [
'iss' => 'your-app', // 签发者
'sub' => $userId, // 用户ID
'iat' => $issuedAt, // 签发时间
'exp' => $expiration, // 过期时间
'jti' => uniqid('', true), // 唯一标识
'permissions' => $permissions // 权限
];
return JWT::encode($payload, $this->secretKey, 'HS256');
}
// 验证短时凭证
public function validateToken($token) {
try {
$decoded = JWT::decode($token, new Key($this->secretKey, 'HS256'));
// 检查是否过期
if ($decoded->exp < time()) {
return ['valid' => false, 'error' => 'Token expired'];
}
return [
'valid' => true,
'user_id' => $decoded->sub,
'permissions' => $decoded->permissions
];
} catch (\Exception $e) {
return ['valid' => false, 'error' => $e->getMessage()];
}
}
// 刷新凭证
public function refreshToken($oldToken) {
$validated = $this->validateToken($oldToken);
if ($validated['valid']) {
return $this->generateToken(
$validated['user_id'],
$validated['permissions'] ?? []
);
}
return null;
}
}
// 使用示例
$credential = new ShortLivedCredential('your-secret-key', 3600);
$token = $credential->generateToken(123, ['read', 'write']);
echo "Generated Token: " . $token . "\n";
$result = $credential->validateToken($token);
var_dump($result);
基于Redis的短时凭证(可随时撤销)
class RedisShortLivedCredential {
private $redis;
private $prefix;
public function __construct($redis, $prefix = 'cred:') {
$this->redis = $redis;
$this->prefix = $prefix;
}
// 生成短时凭证
public function createCredential($userId, $permissions = [], $ttl = 3600) {
$token = bin2hex(random_bytes(32)); // 生成随机token
$credentialData = [
'user_id' => $userId,
'permissions' => $permissions,
'created_at' => time(),
'expires_at' => time() + $ttl
];
// 存储到Redis,设置过期时间
$this->redis->setex(
$this->prefix . $token,
$ttl,
json_encode($credentialData)
);
return $token;
}
// 验证凭证
public function validateCredential($token) {
$data = $this->redis->get($this->prefix . $token);
if (!$data) {
return null;
}
$credential = json_decode($data, true);
// 检查过期时间
if ($credential['expires_at'] < time()) {
$this->revokeCredential($token);
return null;
}
return $credential;
}
// 撤销凭证
public function revokeCredential($token) {
$this->redis->del($this->prefix . $token);
}
// 延长凭证有效期
public function extendCredential($token, $additionalTime = 1800) {
$credential = $this->validateCredential($token);
if ($credential) {
$newExpiry = time() + $additionalTime;
$credential['expires_at'] = $newExpiry;
$this->redis->setex(
$this->prefix . $token,
$additionalTime,
json_encode($credential)
);
return true;
}
return false;
}
}
// 使用示例
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$credential = new RedisShortLivedCredential($redis);
// 创建短时凭证(10分钟有效)
$token = $credential->createCredential(123, ['read'], 600);
echo "Token: " . $token . "\n";
// 验证凭证
$data = $credential->validateCredential($token);
var_dump($data);
// 撤销凭证
$credential->revokeCredential($token);
基于数据库的短时凭证
class DatabaseShortLivedCredential {
private $db; // PDO实例
public function __construct($pdo) {
$this->db = $pdo;
}
// 创建短时凭证
public function createCredential($userId, $permissions = [], $ttl = 3600) {
$token = bin2hex(random_bytes(32));
$hash = hash('sha256', $token); // 存储哈希值更安全
$sql = "INSERT INTO short_credentials
(user_id, token_hash, permissions, expires_at, created_at)
VALUES (?, ?, ?, ?, NOW())";
$stmt = $this->db->prepare($sql);
$stmt->execute([
$userId,
$hash,
json_encode($permissions),
date('Y-m-d H:i:s', time() + $ttl)
]);
return $token;
}
// 验证凭证
public function validateCredential($token) {
$hash = hash('sha256', $token);
$sql = "SELECT * FROM short_credentials
WHERE token_hash = ? AND expires_at > NOW()";
$stmt = $this->db->prepare($sql);
$stmt->execute([$hash]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result) {
$result['permissions'] = json_decode($result['permissions'], true);
return $result;
}
return null;
}
// 清理过期凭证
public function cleanupExpired() {
$sql = "DELETE FROM short_credentials WHERE expires_at <= NOW()";
return $this->db->exec($sql);
}
}
// 数据库表结构
/*
CREATE TABLE short_credentials (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
token_hash VARCHAR(64) NOT NULL UNIQUE,
permissions TEXT,
expires_at DATETIME NOT NULL,
created_at DATETIME NOT NULL,
INDEX idx_expires_at (expires_at)
);
*/
一次性短时凭证(用于特定操作)
class OneTimeCredential {
private $cache;
public function __construct($cache) {
$this->cache = $cache; // 可以是Redis或Memcached
}
// 生成一次性凭证
public function generateOneTimeCredential($action, $data, $ttl = 300) {
$token = bin2hex(random_bytes(16));
$credential = [
'action' => $action,
'data' => $data,
'used' => false,
'expiry' => time() + $ttl
];
$this->cache->set("otc:{$token}", json_encode($credential), $ttl);
return $token;
}
// 使用并消耗凭证
public function consumeCredential($token) {
$key = "otc:{$token}";
$data = $this->cache->get($key);
if (!$data) {
return null;
}
$credential = json_decode($data, true);
// 检查是否已使用
if ($credential['used']) {
return null;
}
// 标记为已使用并删除(一次性)
$this->cache->del($key);
$credential['used'] = true;
return $credential;
}
}
// 使用示例(重置密码场景)
$otc = new OneTimeCredential($redis);
// 生成重置密码凭证
$token = $otc->generateOneTimeCredential('password_reset', [
'user_id' => 123,
'expires' => time() + 300
]);
echo "重置密码链接: " . $token . "\n";
// 用户点击链接验证
$result = $otc->consumeCredential($token);
if ($result) {
echo "凭证有效,可以做密码重置操作";
}
安全最佳实践
class SecureCredentialManager {
// 生成安全随机token
private function generateSecureToken() {
return bin2hex(random_bytes(32));
}
// 对称加密(用于需要在客户端存储的凭证)
public function encryptCredential($data, $secretKey) {
$method = 'AES-256-CBC';
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method));
$encrypted = openssl_encrypt(
json_encode($data),
$method,
$secretKey,
0,
$iv
);
return base64_encode($iv . '::' . $encrypted);
}
// 解密凭证
public function decryptCredential($encryptedData, $secretKey) {
list($iv, $encrypted) = explode('::', base64_decode($encryptedData), 2);
$method = 'AES-256-CBC';
$decrypted = openssl_decrypt(
$encrypted,
$method,
$secretKey,
0,
$iv
);
return json_decode($decrypted, true);
}
// 凭证指纹(用于追踪和验证)
public function generateFingerprint($request) {
$userAgent = $request->userAgent() ?? '';
$ip = $request->ip() ?? '';
return hash('sha256', $userAgent . $ip);
}
// 速率限制
public function rateLimit($userId, $tokenType, $limit = 5, $period = 300) {
$key = "rate_limit:{$userId}:{$tokenType}";
$current = $this->cache->get($key) ?: 0;
if ($current >= $limit) {
throw new \Exception('Too many token requests');
}
$this->cache->setex($key, $period, $current + 1);
return true;
}
}
选择建议
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JWT | 无状态、跨语言、易于扩展 | 难以撤销 | Web API、SPA应用 |
| Redis | 可撤销、可控制,内存存储 | 需要Redis服务 | 需要会话管理、实时撤销 |
| 数据库 | 持久化、可审计 | 数据库压力大 | 企业级应用、审计日志 |
| 一次性凭证 | 更高安全性 | 使用不便 | 密码重置、邮件验证 |
核心原则:
- 始终使用 HTTPS
- 凭证有效期尽量短(建议15分钟-2小时)
- 存储时使用哈希处理
- 设置合适的过期时间
- 实现续期/刷新机制
- 做好异常处理和日志记录
选择哪种方案取决于你的具体需求、基础设施和应用场景。