You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在纯PHP中创建刷新令牌?不依赖OAuth等第三方服务

嘿,我懂你要找的是纯PHP实现刷新令牌的方案,不用依赖OAuth那些第三方服务对吧?其实刷新令牌的核心逻辑比JWT简单不少,咱们一步步来理清楚,结合你现有的JWT代码来补全。

纯PHP实现刷新令牌(无OAuth)

刷新令牌的核心逻辑

首先得明确:刷新令牌是一个长期有效的、加密安全的随机字符串,它的唯一作用就是在JWT过期后,换取新的JWT。和JWT不同,它不携带任何用户数据,只是一个与用户绑定的凭证,而且必须存在服务器端的数据库中(方便验证和作废)。

生成刷新令牌的安全方法

绝对不能用rand()、uniqid()这类不安全的函数生成刷新令牌,必须用PHP提供的加密安全随机函数:

function generateRefreshToken() {
    // 生成32字节的加密安全随机字符串,转成十六进制后是64位,足够抵御暴力破解
    return bin2hex(random_bytes(32));
}

random_bytes()是PHP 7+内置的函数,生成的随机数符合密码学安全标准,完全不用担心被猜测。

存储与配合JWT的完整流程

1. 用户登录时的流程

  • 生成短有效期的JWT(比如15-30分钟,避免JWT泄露后被长期滥用)
  • 生成长有效期的刷新令牌(比如7-30天,根据业务需求调整)
  • 将刷新令牌与用户ID、过期时间、状态(是否作废)一起存入数据库
  • 把JWT和刷新令牌返回给客户端(JWT存在localStorage或内存,刷新令牌建议存在HttpOnly+Secure的Cookie中,防止XSS攻击)

2. JWT过期后的刷新流程

  • 客户端携带刷新令牌请求专门的/refresh-token接口
  • 服务器验证刷新令牌:检查数据库中是否存在该令牌、是否未过期、是否未被作废
  • 验证通过后,生成新的JWT返回给客户端;可选生成新的刷新令牌(刷新令牌轮转,进一步提升安全性)
  • 验证失败则返回401,要求用户重新登录

结合你现有JWT代码的完整示例

先补全你未写完的JWT创建函数,再整合刷新令牌的逻辑:

// 建议将密钥存在环境变量中,不要硬编码!
$secret = getenv('JWT_SECRET') ?: 'your_strong_random_secret_here';

// JWT头部
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);

/**
 * 创建短有效期的JWT
 * @param int $user_id 关联的用户ID
 * @return string 生成的JWT
 */
function createJWT($user_id) {
    global $header, $secret;
    
    $currentTime = new DateTime();
    $iat = $currentTime->getTimestamp();
    // 设置JWT有效期为15分钟
    $exp = $iat + 15 * 60;
    
    // 构建Payload
    $payload = json_encode([
        'user_id' => $user_id,
        'iat' => $iat, // 签发时间
        'exp' => $exp  // 过期时间
    ]);
    
    // Base64Url编码(替换JWT不允许的字符)
    $base64Header = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));
    $base64Payload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));
    
    // 生成签名
    $signature = hash_hmac('sha256', $base64Header . "." . $base64Payload, $secret, true);
    $base64Signature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
    
    // 拼接成最终JWT
    return $base64Header . "." . $base64Payload . "." . $base64Signature;
}

/**
 * 生成加密安全的刷新令牌
 * @return string 刷新令牌
 */
function generateRefreshToken() {
    return bin2hex(random_bytes(32));
}

/**
 * 模拟登录接口逻辑
 * @param int $user_id 用户ID
 * @return string JSON响应
 */
function handleLogin($user_id) {
    // 生成JWT
    $jwt = createJWT($user_id);
    
    // 生成刷新令牌
    $refreshToken = generateRefreshToken();
    
    // 计算刷新令牌过期时间(7天后)
    $expiresAt = (new DateTime())->modify('+7 days')->format('Y-m-d H:i:s');
    
    // 将刷新令牌存入数据库(示例用PDO,实际请根据你的数据库连接方式调整)
    /*
    $pdo = new PDO('mysql:host=localhost;dbname=your_database', 'username', 'password');
    $stmt = $pdo->prepare("INSERT INTO refresh_tokens (user_id, token, expires_at, is_revoked) VALUES (?, ?, ?, 0)");
    $stmt->execute([$user_id, $refreshToken, $expiresAt]);
    */
    
    // 返回给客户端的响应
    return json_encode([
        'access_token' => $jwt,
        'refresh_token' => $refreshToken,
        'token_type' => 'Bearer',
        'expires_in' => 15 * 60 // 单位:秒
    ]);
}

/**
 * 模拟刷新令牌接口逻辑
 * @param string $refreshToken 客户端传来的刷新令牌
 * @return string JSON响应
 */
function handleRefreshToken($refreshToken) {
    // 从数据库查询刷新令牌的有效性
    /*
    $pdo = new PDO('mysql:host=localhost;dbname=your_database', 'username', 'password');
    $stmt = $pdo->prepare("SELECT * FROM refresh_tokens WHERE token = ? AND is_revoked = 0 AND expires_at > NOW()");
    $stmt->execute([$refreshToken]);
    $tokenRecord = $stmt->fetch(PDO::FETCH_ASSOC);
    */
    
    // 模拟数据库查询结果(实际需从数据库获取)
    $tokenRecord = [
        'user_id' => $user_id, // 替换为实际用户ID
        'token' => $refreshToken
    ];
    
    if (!$tokenRecord) {
        // 刷新令牌无效,返回401未授权
        http_response_code(401);
        return json_encode(['error' => 'Invalid or expired refresh token']);
    }
    
    // 生成新的JWT
    $newJwt = createJWT($tokenRecord['user_id']);
    
    // 可选:刷新令牌轮转(生成新的刷新令牌,作废旧的,提升安全性)
    $newRefreshToken = generateRefreshToken();
    $newExpiresAt = (new DateTime())->modify('+7 days')->format('Y-m-d H:i:s');
    /*
    $stmt = $pdo->prepare("UPDATE refresh_tokens SET token = ?, expires_at = ? WHERE token = ?");
    $stmt->execute([$newRefreshToken, $newExpiresAt, $refreshToken]);
    */
    
    // 返回新的令牌
    return json_encode([
        'access_token' => $newJwt,
        'refresh_token' => $newRefreshToken, // 如果不轮转,返回原$refreshToken即可
        'token_type' => 'Bearer',
        'expires_in' => 15 * 60
    ]);
}

关键安全注意事项

  • 刷新令牌必须存储在服务器端:不能像JWT那样只靠签名验证,因为刷新令牌是长期有效的,一旦泄露必须能通过数据库标记作废。
  • 客户端存储刷新令牌的安全方式:优先使用HttpOnly、Secure、SameSite属性的Cookie,避免存在localStorage(防止XSS攻击窃取)。
  • 密钥安全:绝对不要把JWT密钥硬编码在代码里,用环境变量或者配置文件存储,且密钥要足够长(至少32位随机字符串)。
  • 刷新令牌轮转:每次刷新JWT时生成新的刷新令牌,旧的立即作废,这样即使旧令牌泄露也无法被滥用。
  • JWT验证:别忘了实现JWT的验证逻辑,每次请求都要检查JWT的签名、过期时间、用户ID是否合法。

内容的提问来源于stack exchange,提问作者Mikael Svensson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:04:40