使用firebase/php-jwt生成刷新令牌及过期后重验证的实现方法
基于firebase/php-jwt的Refresh Token实现方案
一、Refresh Token下发逻辑
Refresh Token和短有效期的Access Token配套使用,核心作用是避免用户频繁输入账号密码登录,前者有效期通常设为730天,后者设为1530分钟。下发逻辑如下:
- 首先确认你使用的Refresh Token方案,两类方案可选:
- 有状态方案(生产环境推荐):用
bin2hex(random_bytes(32))生成64位随机唯一字符串作为Refresh Token,将该字符串、对应用户ID、过期时间、设备标识(可选)存储到数据库/Redis中,无需封装为JWT格式。 - 无状态方案:直接用JWT签发Refresh Token,和Access Token使用不同的签名密钥,Payload仅存储用户ID这类基础信息,不要存储权限等敏感字段。
- 有状态方案(生产环境推荐):用
- 下发时机:用户登录验证账号密码通过后,和Access Token同时返回给客户端,返回字段一般包含
access_token、refresh_token、expires_in(Access Token的有效秒数)。
生产环境优先选择有状态的Refresh Token方案,可满足主动作废、限制多设备登录、被盗后快速冻结等安全需求。
二、令牌刷新逻辑
客户端检测到Access Token即将过期时,调用服务端刷新接口,携带Refresh Token请求新的Access Token,服务端验证逻辑如下:
有状态Refresh Token验证步骤
- 取客户端传入的Refresh Token,到存储层查询是否存在对应记录
- 检查记录的过期时间,如果已过期直接返回验证失败
- 验证记录绑定的用户状态正常,无封禁、注销等情况
- 验证通过后生成新的Access Token,同时可生成新的Refresh Token实现滑动有效期,将旧Refresh Token标记为失效后,返回新的双Token给客户端
无状态JWT格式Refresh Token验证步骤
- 用专属的Refresh Token签名密钥解密JWT,验证签名合法性
- 校验过期时间,过期直接返回验证失败
- 取出Payload中的用户ID,验证用户状态正常
- 生成新的Access Token返回,如需滑动有效期则同步签发新的Refresh Token返回
三、Refresh Token过期后的重验证机制
- 只要Refresh Token验证不通过(过期、不存在、已作废、签名错误),服务端统一返回
401 Unauthorized状态码,可搭配自定义业务码标识登录失效 - 客户端收到对应状态码/业务码后,直接清空本地存储的所有Token,跳转至登录页面,要求用户重新输入账号密码完成身份校验
- 可选安全增强:同一个Refresh Token仅允许使用一次,每次刷新就生成新的Token,若收到已使用过的Refresh Token的刷新请求,直接作废该用户所有生效的Refresh Token,强制用户重新登录,降低Token盗用风险
简易代码示例
双Token下发代码
// 初始化配置 $accessSecret = '你的Access Token专属签名密钥'; $refreshSecret = '你的Refresh Token专属签名密钥'; $accessExp = 15 * 60; // Access Token有效期15分钟 $refreshExp = 7 * 24 * 3600; // Refresh Token有效期7天 // 账号密码验证通过后拿到用户ID $userId = 10001; // 签发Access Token $accessPayload = [ 'iss' => '你的服务标识', 'iat' => time(), 'exp' => time() + $accessExp, 'uid' => $userId, 'role' => '普通用户' ]; $accessToken = Firebase\JWT\JWT::encode($accessPayload, $accessSecret, 'HS256'); // 签发无状态Refresh Token $refreshPayload = [ 'iss' => '你的服务标识', 'iat' => time(), 'exp' => time() + $refreshExp, 'uid' => $userId ]; $refreshToken = Firebase\JWT\JWT::encode($refreshPayload, $refreshSecret, 'HS256'); // 返回给客户端 echo json_encode([ 'access_token' => $accessToken, 'refresh_token' => $refreshToken, 'expires_in' => $accessExp ]);
刷新接口代码
try { $clientRefreshToken = $_POST['refresh_token']; // 验证Refresh Token合法性 $decoded = Firebase\JWT\JWT::decode($clientRefreshToken, new Firebase\JWT\Key($refreshSecret, 'HS256')); $userId = $decoded->uid; // 此处省略用户状态合法性校验逻辑 // 生成新的Access Token $newAccessPayload = [ 'iss' => '你的服务标识', 'iat' => time(), 'exp' => time() + $accessExp, 'uid' => $userId, 'role' => '普通用户' ]; $newAccessToken = Firebase\JWT\JWT::encode($newAccessPayload, $accessSecret, 'HS256'); // 可选生成新的Refresh Token实现滑动有效期 $newRefreshPayload = [ 'iss' => '你的服务标识', 'iat' => time(), 'exp' => time() + $refreshExp, 'uid' => $userId ]; $newRefreshToken = Firebase\JWT\JWT::encode($newRefreshPayload, $refreshSecret, 'HS256'); // 返回新Token echo json_encode([ 'access_token' => $newAccessToken, 'refresh_token' => $newRefreshToken, 'expires_in' => $accessExp ]); } catch (Firebase\JWT\ExpiredException $e) { // Refresh Token过期 http_response_code(401); echo json_encode(['code' => 10010, 'msg' => '登录状态已过期,请重新登录']); } catch (Exception $e) { // 其他验证失败 http_response_code(401); echo json_encode(['code' => 10010, 'msg' => '登录状态无效,请重新登录']); }
内容的提问来源于stack exchange,提问作者HOSENUR
相关产品推荐
相关产品推荐

