共计 2556 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点
在微服务架构中,API Token 的有效期管理是系统安全和用户体验之间的重要平衡点。固定有效期的 Token 虽然简单,但存在几个明显的痛点:

- 安全风险 :如果 Token 被泄露,攻击者可以在其有效期内肆意使用,导致窗口期攻击。
- 用户体验 :Token 过期后用户需要频繁重新登录,尤其是在移动端应用中,这会显著降低用户体验。
- 错误率 :据统计,Token 失效引发的接口错误率在某些业务场景下可以占到总错误率的 30% 以上。
技术方案
短期 JWT + Refresh Token
相比于传统的 Session 方案,短期 JWT(如 15 分钟有效期)配合长时效 Refresh Token(如 7 天)的方案有以下优势:
- 安全性 :短期 JWT 降低了泄露后的风险窗口。
- 无状态 :JWT 本身是无状态的,减少了服务端的存储压力。
- 自动续期 :Refresh Token 可以在用户无感知的情况下自动续期 Token,避免频繁登录。
OAuth2 Refresh Token 工作流程
以下是 OAuth2 中 Refresh Token 的工作流程:
- 用户首次登录,获取 Access Token 和 Refresh Token。
- Access Token 过期后,客户端使用 Refresh Token 向认证服务器请求新的 Access Token。
- 认证服务器验证 Refresh Token 的有效性,颁发新的 Access Token(可选颁发新的 Refresh Token)。
- 客户端使用新的 Access Token 继续访问资源。
代码实现
Spring Security OAuth2 配置
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()
.withClient("client-id")
.secret("client-secret")
.authorizedGrantTypes("password", "refresh_token")
.scopes("read", "write")
.accessTokenValiditySeconds(900) // 15 分钟
.refreshTokenValiditySeconds(604800); // 7 天
}
}
JWT 签名验证配置
@Bean
public JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("my-secret-key");
return converter;
}
Refresh Token 存储与校验
推荐使用 Redis 存储 Refresh Token,以下是一个简单的实现:
public class RedisTokenStore implements TokenStore {
private final RedisTemplate<String, Object> redisTemplate;
@Override
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {redisTemplate.opsForValue().set("refresh_token:" + refreshToken.getValue(),
authentication,
refreshToken.getExpiration().getTime() - System.currentTimeMillis(),
TimeUnit.MILLISECONDS
);
}
}
生产环境考量
Token 吊销清单(Blacklist)
实现一个 Token 黑名单,用于在用户登出或 Token 被撤销时立即失效 Token。可以使用 Redis 的过期特性来实现:
public void revokeToken(String token) {redisTemplate.opsForValue().set(
"blacklist:" + token,
"revoked",
getExpirationTime(token) - System.currentTimeMillis(),
TimeUnit.MILLISECONDS
);
}
防重放攻击的 Nonce 机制
为了防止 Token 被截获后重放,可以在 JWT 中加入 Nonce(一次性随机数):
public String generateToken(User user) {String nonce = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("nonce:" + nonce, "used", 15, TimeUnit.MINUTES);
return Jwts.builder()
.setSubject(user.getUsername())
.claim("nonce", nonce)
.setExpiration(new Date(System.currentTimeMillis() + 900000))
.signWith(SignatureAlgorithm.HS256, "my-secret-key")
.compact();}
避坑指南
- Refresh Token 存储 :Refresh Token 必须安全存储,建议加密后存储在数据库中或使用安全的存储服务。
- TTL 设计 :避免 Token 无限续期,可以通过限制 Refresh Token 的使用次数或设置绝对过期时间来控制。
- 性能优化 :跨服务验证 Token 时,可以使用本地缓存来减少对认证服务的压力。
开放性问题
如何平衡 Token 有效期与业务安全等级要求?不同的业务场景(如金融 vs 社交)对安全性和用户体验的需求不同,如何根据业务特点定制 Token 策略?
正文完
