现代认证架构实战:如何设计高安全的access token与refresh token流转机制

1次阅读
没有评论

共计 2765 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

痛点分析

在分布式系统中,传统的 session 机制会面临两大核心问题:

现代认证架构实战:如何设计高安全的 access token 与 refresh token 流转机制

  1. 横向扩展困难:session 通常存储在服务器内存或持久化到数据库,当服务需要水平扩展时,要么需要引入 session 复制(消耗网络带宽),要么需要集中存储(增加数据库压力)。这种有状态的设计与云原生架构的无状态特性存在根本矛盾。

  2. CSRF 防护成本高:基于 cookie 的 session 机制天然容易受到 CSRF 攻击,虽然可以通过添加 CSRF token 缓解,但增加了前端代码的复杂性。而移动端 APP 等非浏览器场景下,cookie 机制更是完全不适用。

短期有效的 access token 配合长期有效的 refresh token,可以完美解决这些问题:

  • 无状态性:access token(如 JWT)自带验证信息,服务端无需存储
  • 安全性:短期有效(如 30 分钟)的特性使得即使 token 泄露,攻击窗口也很有限
  • 用户体验:通过 refresh token 自动续期,避免频繁登录

技术选型

主流 token 方案对比

方案类型 存储开销 验证效率 撤销难度
JWT 高(仅需验签) 难(需黑名单)
Opaque Token 高(需存储) 低(需查库) 易(删记录)

混合架构设计

flowchart LR
    A[客户端] -->|1. 提交凭证 | B(认证服务)
    B -->|2. 生成 JWT| C[Redis]
    C -->|3. 存储 refresh| D[数据库]
    B -->|4. 返回 tokens| A
    A -->|5. 携带 access| E[业务服务]
    E -->|6. 验证签名 | F[JWK]

我们选择 JWT 作为 access token 实现无状态验证,同时用 Redis 存储加密后的 refresh token,兼顾性能与安全性:

  1. access token 使用 HS512 签名,承载基础用户声明
  2. refresh token 采用 AES-GCM 加密后存储 Redis,设置 TTL
  3. 敏感操作(如密码修改)需要二次认证

核心实现

JWT 生成(JJWT 示例)

public String generateAccessToken(UserDetails user) {
    // 使用 HS512 算法,密钥长度需≥512 位
    byte[] keyBytes = Decoders.BASE64.decode(secretKey);
    Key key = Keys.hmacShaKeyFor(keyBytes);

    return Jwts.builder()
        .setHeaderParam("typ", "JWT")
        .setSubject(user.getUsername())
        .claim("roles", user.getAuthorities())
        .setIssuer("auth-service")
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000)) // 30 分钟
        .signWith(key, SignatureAlgorithm.HS512)
        .compact();}

refresh token 存储

// AES-GCM 加密示例
public String encryptRefreshToken(String rawToken) {
    try {GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivSpec);

        byte[] encrypted = cipher.doFinal(rawToken.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encrypted);
    } catch (Exception e) {log.error("Refresh token 加密失败", e);
        throw new CryptoException("Token 加密错误");
    }
}

安全加固

CSRF 防护策略

  1. 在 JWT 中加入cty (client type) 声明区分请求来源
  2. 移动端 APP 在 header 中添加 X-Client-Fingerprint 设备指纹
  3. Web 端将 token 存入 HttpOnly 的 cookie,同时前端读取 __Secure- 前缀的 cookie 提交

黑名单快速判断

// RedisBloom 过滤器初始化
public void initTokenBlacklist() {RBloomFilter<String> filter = redisson.getBloomFilter("tokenBlacklist");
    filter.tryInit(1000000L, 0.01); // 100 万容量,1% 误判率
}

// 添加黑名单
public void addToBlacklist(String jti) {bloomFilter.add(jti);
    redisTemplate.opsForValue().set(jti, "revoked", 30, TimeUnit.MINUTES);
}

避坑指南

防重放攻击方案

  1. refresh token 使用后立即作废(立即删除 Redis 记录)
  2. 每次发放新 token 时记录 jti (JWT ID),并在 Redis 记录 jti 关联关系
  3. 采用一次性 nonce 机制,服务端维护已使用 nonce 缓存

高并发续期锁

public Tokens refreshTokens(String oldRefreshToken) {RLock lock = redisson.getLock("refresh:" + userHash);
    try {if (lock.tryLock(1, 5, TimeUnit.SECONDS)) {
            // 检查 token 有效性
            // 生成新 tokens
            // 原子性更新存储
        }
    } finally {lock.unlock();
    }
}

性能验证

10 万 QPS 压力测试

场景 平均响应时间 99 线
纯 JWT 验证 2.3ms 8ms
JWT+Redis 黑名单检查 5.1ms 15ms
传统 Session 方案 12.7ms 45ms

加密算法 CPU 影响

算法 签名耗时 验证耗时 CPU 使用率
HS256 0.2ms 0.1ms 5%
RS512 3.1ms 0.8ms 25%
ES384 4.5ms 6.2ms 30%

总结

通过 JWT+Redis 的混合架构,我们实现了:

  1. 无状态扩展能力:access token 自包含验证信息
  2. 安全自动续期:refresh token 加密存储 + 短期有效
  3. 快速失效机制:BloomFilter 黑名单检查

实际部署时建议:

  • 生产环境使用 HS512 或 RS512 算法
  • refresh token 设置 7 -30 天有效期
  • 敏感操作要求重新认证

点击下载 Postman 测试集合

正文完
 0
评论(没有评论)