本文目录导读:

在 PHP 中实现令牌(Token)刷新通常指的是OAuth2 刷新令牌或CSRF 令牌刷新,以下是几种常见场景的完整解决方案:
OAuth2 刷新令牌(Access Token 刷新)
1 数据库表结构
CREATE TABLE oauth_tokens (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
access_token VARCHAR(255) NOT NULL,
refresh_token VARCHAR(255) NOT NULL,
access_expires_at DATETIME NOT NULL,
refresh_expires_at DATETIME NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_access_token (access_token),
INDEX idx_refresh_token (refresh_token)
);
2 刷新令牌实现
<?php
class TokenManager {
private $db;
public function __construct($pdo) {
$this->db = $pdo;
}
// 生成新令牌
public function generateTokenPair($userId) {
$accessToken = bin2hex(random_bytes(32)); // 64字符
$refreshToken = bin2hex(random_bytes(32));
$accessExpires = date('Y-m-d H:i:s', time() + 3600); // 1小时有效期
$refreshExpires = date('Y-m-d H:i:s', time() + 2592000); // 30天有效期
$stmt = $this->db->prepare("
INSERT INTO oauth_tokens
(user_id, access_token, refresh_token, access_expires_at, refresh_expires_at)
VALUES (?, ?, ?, ?, ?)
");
$stmt->execute([
$userId,
$accessToken,
$refreshToken,
$accessExpires,
$refreshExpires
]);
return [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'access_expires_in' => 3600,
'refresh_expires_in' => 2592000
];
}
// 刷新访问令牌
public function refreshAccessToken($refreshToken) {
// 查找有效的刷新令牌
$stmt = $this->db->prepare("
SELECT * FROM oauth_tokens
WHERE refresh_token = ?
AND refresh_expires_at > NOW()
AND revoked_at IS NULL
LIMIT 1
");
$stmt->execute([$refreshToken]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$existing) {
throw new Exception('Refresh token无效或已过期');
}
// 撤销旧刷新令牌
$this->revokeToken($existing['id']);
// 生成新的令牌对
return $this->generateTokenPair($existing['user_id']);
}
// 撤销令牌
private function revokeToken($tokenId) {
$stmt = $this->db->prepare("
UPDATE oauth_tokens
SET revoked_at = NOW()
WHERE id = ?
");
$stmt->execute([$tokenId]);
}
}
3 使用示例
// 刷新端点到 REST API
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_GET['action'] === 'refresh') {
header('Content-Type: application/json');
$json = json_decode(file_get_contents('php://input'), true);
$refreshToken = $json['refresh_token'] ?? null;
if (!$refreshToken) {
http_response_code(400);
echo json_encode(['error' => '缺少刷新令牌']);
exit;
}
try {
$tokenManager = new TokenManager($pdo);
$newTokens = $tokenManager->refreshAccessToken($refreshToken);
echo json_encode([
'success' => true,
'data' => $newTokens
]);
} catch (Exception $e) {
http_response_code(401);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
}
CSRF 令牌刷新
1 Session 中的 CSRF 令牌
<?php
class CSRFProtection {
public static function generateToken() {
// 如果已有会话中的令牌,先销毁
if (isset($_SESSION['csrf_token'])) {
unset($_SESSION['csrf_token']);
}
// 生成新令牌
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
return $_SESSION['csrf_token'];
}
public static function validateToken($token) {
return isset($_SESSION['csrf_token'])
&& hash_equals($_SESSION['csrf_token'], $token);
}
public static function refreshToken() {
return self::generateToken();
}
}
2 每次请求刷新 CSRF
// 页面加载时,生成或刷新令牌
session_start();
$csrfToken = CSRFProtection::generateToken();
?>
<!-- HTML 表单示例 -->
<form method="POST" action="/submit.php">
<input type="hidden" name="csrf_token" value="<?php echo $csrfToken; ?>">
<!-- 其他表单字段 -->
<button type="submit">提交</button>
</form>
<!-- AJAX 请求示例 -->
<button onclick="submitAjax()">AJAX提交</button>
<script>
async function submitAjax() {
const response = await fetch('/api/endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': '<?php echo $csrfToken; ?>' // 前端需要保存这个值
},
body: JSON.stringify({ data: 'example' })
});
}
</script>
3 处理提交并刷新
// submit.php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$csrfToken = $_POST['csrf_token'] ?? null;
if (!CSRFProtection::validateToken($csrfToken)) {
die('CSRF验证失败');
}
// 处理表单数据...
// 刷新 CSRF 令牌(每次提交后自动刷新)
$newCsrfToken = CSRFProtection::refreshToken();
// 返回新令牌供前端使用
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'new_csrf_token' => $newCsrfToken
]);
}
JWT 令牌刷新
1 JWT 刷新实现
<?php
class JWTManager {
private $secretKey;
private $refreshtokenSecret;
public function __construct() {
$this->secretKey = 'your-access-token-secret';
$this->refreshtokenSecret = 'your-refresh-token-secret';
}
public function generateAccessToken($userId, $claims = []) {
$payload = array_merge([
'sub' => $userId,
'iat' => time(),
'exp' => time() + 3600, // 1小时过期
'type' => 'access'
], $claims);
return $this->createJWT($payload, $this->secretKey);
}
public function generateRefreshToken($userId) {
$payload = [
'sub' => $userId,
'iat' => time(),
'exp' => time() + 2592000, // 30天过期
'type' => 'refresh',
'jti' => bin2hex(random_bytes(16)) // 唯一ID,用于撤销
];
return $this->createJWT($payload, $this->refreshtokenSecret);
}
public function refreshAccessToken($refreshToken) {
// 验证刷新令牌
$decoded = $this->validateJWT($refreshToken, $this->refreshtokenSecret);
if (!$decoded) {
throw new Exception('刷新令牌无效');
}
if ($decoded->type !== 'refresh') {
throw new Exception('不是有效的刷新令牌');
}
// 检查是否在撤销列表中(实现时可存储到 Redis/数据库)
if ($this->isRevoked($decoded->jti)) {
throw new Exception('刷新令牌已被撤销');
}
// 生成新的访问令牌
return $this->generateAccessToken($decoded->sub);
}
private function createJWT($payload, $secret) {
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
$base64Header = $this->base64UrlEncode($header);
$base64Payload = $this->base64UrlEncode(json_encode($payload));
$signature = hash_hmac('sha256',
"$base64Header.$base64Payload",
$secret,
true
);
$base64Signature = $this->base64UrlEncode($signature);
return "$base64Header.$base64Payload.$base64Signature";
}
private function validateJWT($jwt, $secret) {
$parts = explode('.', $jwt);
if (count($parts) !== 3) return false;
list($header, $payload, $signature) = $parts;
// 验证签名
$expectedSig = $this->base64UrlEncode(
hash_hmac('sha256', "$header.$payload", $secret, true)
);
if (!hash_equals($expectedSig, $signature)) {
return false;
}
// 验证过期时间
$decoded = json_decode($this->base64UrlDecode($payload));
if ($decoded->exp < time()) {
return false;
}
return $decoded;
}
private function base64UrlEncode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
private function base64UrlDecode($data) {
return base64_decode(strtr($data, '-_', '+/'));
}
private function isRevoked($jti) {
// 实现撤销逻辑:检查 Redis/数据库中的黑名单
return false; // 示例中默认不撤销
}
}
最佳实践
1 安全建议
- 令牌存储:使用
random_bytes()生成高强度随机令牌 - 令牌过期:访问令牌应设置较短有效期(15-60分钟),刷新令牌可长一些(7-30天)
- 令牌撤销:实现撤销机制,防止旧令牌被重用
- 使用 HTTPS:所有令牌传输必须加密
2 自动刷新逻辑(客户端)
// 前端自动刷新访问令牌
let isRefreshing = false;
async function makeAuthenticatedRequest(url, options = {}) {
let accessToken = localStorage.getItem('access_token');
options.headers = {
'Authorization': `Bearer ${accessToken}`,
...options.headers
};
let response = await fetch(url, options);
// 遇到 401 时尝试刷新
if (response.status === 401 && !isRefreshing) {
isRefreshing = true;
const refreshToken = localStorage.getItem('refresh_token');
const refreshResponse = await fetch('/api/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refreshToken })
});
if (refreshResponse.ok) {
const data = await refreshResponse.json();
localStorage.setItem('access_token', data.access_token);
// 重试原请求
options.headers['Authorization'] = `Bearer ${data.access_token}`;
response = await fetch(url, options);
} else {
// 退出登录
window.location.href = '/login';
}
isRefreshing = false;
}
return response;
}
完整示例(API 端点)
// api/refresh.php - 统一刷新端点
session_start();
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
// CSRF 验证(如适用)
if (!isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $input['csrf_token'])) {
http_response_code(403);
echo json_encode(['error' => 'CSRF验证失败']);
exit;
}
try {
$tokenManager = new TokenManager($pdo);
$refreshToken = $input['refresh_token'] ?? null;
if (!$refreshToken) {
throw new Exception('缺少刷新令牌');
}
$newTokens = $tokenManager->refreshAccessToken($refreshToken);
// 刷新 CSRF 令牌
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// 返回新令牌
echo json_encode([
'access_token' => $newTokens['access_token'],
'refresh_token' => $newTokens['refresh_token'],
'csrf_token' => $_SESSION['csrf_token'],
'expires_in' => 3600
]);
} catch (Exception $e) {
http_response_code(400);
echo json_encode(['error' => $e->getMessage()]);
}
这些实现覆盖了 PHP 中常见的令牌刷新场景,根据你的具体需求选择合适的方式,并确保始终使用安全的随机函数和 HTTPS 连接。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播