双Token设计思路(Refresh Token & Access Token)

核心原则

  • Access Token:短期访问凭证(10-30分钟),用于业务请求

  • Refresh Token:长期刷新凭证(7-30天),用于获取新 Access Token

    • 安全层面:Access Token泄露影响时间短
    • 体验层面:无感刷新避免频繁登录
    • 控制层面:Refresh Token可单独吊销
密码登录
用户
SSO Server
生成Access Token
生成Refresh Token
访问业务资源
安全存储

双 Token 工作流程

UserClientSSOBusinessAPI提交登录发送凭证生成双Token返回Access+Refresh携带Access访问返回数据Access过期时用Refresh请求新Access验证Refresh有效性返回新Access要求重新登录alt[有效][无效]UserClientSSOBusinessAPI

关键设计细节

1. Token 结构

Access Token (JWT)

存储用户详情及各系统权限等

{
  "sub": "user123",
  "exp": 1624290000, // 30分钟后过期
  "scope": "api.read",
  "jti": "a1b2c3d4" // 唯一标识
}

Refresh Token (不透明令牌)

存储基本信息,去除敏感信息,仅用于Access Token刷新

// Redis存储结构
key: refresh:user123:client_mobile
value: {
  "token": "x9y8z7w6",
  "expire_at": 1626872400, // 7天后过期
  "client_info": "iPhone13;IOS15"
}
2. 刷新机制

刷新时机:

  1. 高风险操作后(如修改密码等)
  2. 跨设备登录
  3. 定期更新(7day等)
// SSO Server刷新接口
@PostMapping("/refresh")
public TokenResponse refreshTokens(@RequestParam String refreshToken) {
    // 1. 验证Refresh Token有效性 
    RefreshToken stored = redisTemplate.opsForValue().get(refreshToken);
    if(stored == null || stored.isRevoked()) {
        throw new InvalidTokenException();
    }

    // 2. 生成新Access Token 
    String newAccessToken = JwtUtil.generateToken(stored.getUserId());
    
    // 3. 可选:刷新令牌轮转 
    String newRefreshToken = null;
    if(shouldRotateRefreshToken(stored)) {
        newRefreshToken = generateRefreshToken(stored.getUserId());
        redisTemplate.delete(refreshToken); // 使旧Refresh失效
    }

    return new TokenResponse(newAccessToken, newRefreshToken);
}
3. 安全防护
风险防护方案
Access Token泄露短有效期(≤30min) + 黑名单
Refresh Token窃取绑定设备/IP + 单次使用轮转
重放攻击JWT jti唯一标识 + 一次性验证
CSRF攻击Refresh Token存HttpOnly Cookie

最佳实践

1. 存储策略
Access Token
Refresh Token
Token映射
键设计
前端
内存/SessionStorage
HttpOnly Cookie
后端
Redis集群
user:client:type
2. 注销协同方案
// 全局登出服务
public void globalLogout(String userId) {
    // 1. 清除所有Access Token
    redisTemplate.deletePattern("access:*:" + userId);
    
    // 2. 使Refresh Token失效
    Set<String> refreshKeys = redisTemplate.keys("refresh:*:" + userId);
    refreshKeys.forEach(key -> redisTemplate.delete(key));
    
    // 3. 发布MQ注销事件 
    mqTemplate.send("user_logout", userId);
}
3. 跨域安全方案
# Nginx配置示例
add_header 'Access-Control-Allow-Origin' 'https://yourdomain.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Expose-Headers' 'Authorization';
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值