Spring Boot后端Refresh Token选型:UUID还是JWT?及相关疑问
关于Refresh Token:UUID vs JWT的选型及相关问题解答
一、UUID vs JWT作为Refresh Token的选型分析
UUID方案特点
- 优点:
- Token为完全随机字符串,无任何明文信息泄露风险;
- 所有Token状态存储在数据库,支持主动失效(如用户登出、修改密码时直接删除对应记录);
- 实现逻辑简单,依赖数据库校验合法性。
- 缺点:
- 每次校验Refresh Token都需查询数据库,高并发场景下会增加数据库压力;
- 需要维护
refresh_token表,增加存储成本。
JWT方案特点
- 优点:
- 无需查询数据库即可校验过期时间,性能更优;
- 载荷可存储少量非敏感信息(如用户ID),减少额外业务查询;
- 无状态设计,适配分布式系统架构。
- 缺点:
- 一旦签发无法主动失效,除非引入黑名单机制(但会增加存储依赖);
- 签名密钥泄露后,攻击者可伪造合法Refresh Token;
- 载荷采用Base64编码(非加密),不能存储敏感数据。
选型建议
没有绝对最优方案,需结合业务场景判断:
- 若系统对主动失效需求高(如频繁的用户登出、密码修改操作),或系统规模较小,UUID方案更安全可控;
- 若系统为分布式架构,追求无状态、高并发性能,且可接受Refresh Token无法主动失效(或通过缩短有效期+黑名单机制弥补),则JWT更合适。但必须严格保证签名密钥的安全性,且载荷中禁止存放敏感信息。
二、JWT类型Refresh Token是否需要哈希存储?
必须哈希存储!即使JWT带有签名,存储时也应使用哈希算法(如BCrypt、SHA-256加盐)处理,原因如下:
- 避免数据库泄露后,攻击者直接获取到可直接使用的有效Refresh Token(JWT本身可解码,泄露后能被直接利用);
- 哈希后的Token无法逆向还原,即使数据泄露,攻击者也无法直接使用;
- 哈希时务必加盐,防止彩虹表攻击。
三、额外问题:使用org.springframework.security.oauth2.jwt时,控制器中如何获取过期声明?
场景1:Refresh Token作为Bearer Token在请求头中
Spring Security会自动解析请求头中的JWT,可直接在控制器方法参数中注入Jwt对象获取过期声明:
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class RefreshTokenController { @GetMapping("/check-refresh-expiry") public String getRefreshTokenExpiry(Jwt jwt) { // 获取过期时间的Instant对象 Instant expiryTime = jwt.getExpiresAt(); // 或直接获取"exp"声明的时间戳(Long类型) Long expiryTimestamp = jwt.getClaim("exp"); return "Refresh Token过期时间:" + expiryTime; } }
场景2:Refresh Token放在请求参数/自定义头中
可通过JwtDecoder手动解析Token并获取过期声明:
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class RefreshTokenController { private final JwtDecoder jwtDecoder; // 构造注入JwtDecoder public RefreshTokenController(JwtDecoder jwtDecoder) { this.jwtDecoder = jwtDecoder; } @GetMapping("/verify-refresh-token") public String verifyRefreshToken(@RequestParam String refreshToken) { Jwt jwt = jwtDecoder.decode(refreshToken); Instant expiryTime = jwt.getExpiresAt(); return "Refresh Token过期时间:" + expiryTime; } }
内容的提问来源于stack exchange,提问作者Johnny boy
相关产品推荐
相关产品推荐

