在前后端分离的Spring Boot项目中,JWT以无状态、轻量的优势成为主流身份认证方案,但JWT的致命痛点是一旦签发无法主动修改过期时间:据鳄鱼java社区2026年《身份认证体验调研》显示,68%的用户因Token过期被迫频繁登录而反感系统,22%的用户因此放弃使用。Spring Security JWT Token自动刷新方案的核心价值,就在于通过合理的Token分层设计与自动化刷新逻辑,平衡JWT的无状态特性与用户的无感登录体验,将用户重登频率降低90%,同时保证身份认证的安全性,成为企业级Spring项目中身份认证的标准进阶方案。
为什么需要JWT自动刷新?传统JWT的致命痛点

JWT的核心优势是无状态:服务器无需存储Token信息,只需通过密钥验证签名即可完成认证。但这一优势也带来了无法解决的过期问题:为了保证安全性,Access Token的过期时间通常设置较短(比如15分钟-1小时),用户在操作过程中Token过期后,只能重新登录获取新Token,这会严重打断用户操作流程。
手动刷新Token的方案存在两大问题:一是用户体验差,用户需手动输入账号密码重新登录;二是安全性难以保障,若将Token过期时间设置过长,Token泄露后被滥用的风险极高。鳄鱼java社区的实战案例显示,某电商项目曾将Token过期时间设置为7天,导致Token泄露后被盗刷订单,损失超5万元;而将Token缩短为15分钟后,用户日均重登次数从2次飙升至10次,用户活跃度下降30%。因此,自动刷新方案是平衡安全性与用户体验的唯一解。
核心思路:平衡无状态与自动刷新的三种主流方案
在Spring Security JWT Token自动刷新方案的实践中,开发者通常会采用以下三种方案,各自适配不同的业务场景:
1. 短Access Token+长Refresh Token方案:最成熟、最常用的方案,Access Token有效期短(15-60分钟),Refresh Token有效期长(7-14天),用户用Refresh Token换取新的Access Token。该方案既保证了Access Token泄露后的风险可控,又通过Refresh Token实现自动刷新,是鳄鱼java社区最推荐的方案。
2. Access Token过期前静默刷新方案:前端通过拦截器在每次请求前检查Access Token的过期时间,若剩余时间小于阈值(比如5分钟),则静默调用刷新接口获取新Token,无需用户感知。该方案适合对用户体验要求极高的场景,但需要前端配合实现时间校验。
3. Redis黑名单+可过期Token方案:将Token的过期时间存储在Redis中,后端每次认证时检查Redis中的过期状态,若需刷新则直接生成新Token。该方案需要牺牲部分JWT的无状态特性,但能实现更灵活的Token管控,适合分布式场景下的动态Token刷新。
Spring Security JWT Token自动刷新方案实战:短Token+RefreshToken实现
以下是鳄鱼java社区落地最多的短Access Token+长Refresh Token方案的完整实战步骤,包含后端实现与前端配合逻辑:
1. 基础依赖与配置
首先引入Spring Security、JWT与Redis的核心依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. JWT工具类与RefreshToken存储
实现JWT生成、解析工具类,同时用Redis存储Refresh Token(设置过期时间为7天):
@Component
public class JwtUtil {
private final String secretKey = "your-secret-key-from-config";
private final long accessTokenExpireMs = 3600000L; // 1小时
private final long refreshTokenExpireMs = 604800000L; // 7天
private final StringRedisTemplate redisTemplate;
public String generateAccessToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + accessTokenExpireMs))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public String generateRefreshToken(UserDetails userDetails) {
String refreshToken = Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + refreshTokenExpireMs))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
// 将RefreshToken存入Redis,键为username,值为refreshToken
redisTemplate.opsForValue().set("refresh_token:" + userDetails.getUsername(), refreshToken, refreshTokenExpireMs, TimeUnit.MILLISECONDS);
return refreshToken;
}
}
3. Spring Security过滤器实现自动刷新
自定义过滤器,在Access Token过期时,用Refresh Token换取新的Access Token:
public class JwtRefreshFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final StringRedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String accessToken = authHeader.substring(7);
// 检查Access Token是否过期且无效
if (!jwtUtil.isTokenValid(accessToken) && jwtUtil.isTokenExpired(accessToken)) {
String refreshToken = request.getHeader("Refresh-Token");
if (refreshToken != null && jwtUtil.isTokenValid(refreshToken)) {
String username = jwtUtil.extractUsername(refreshToken);
// 验证Refresh Token是否与Redis中存储的一致
String storedRefreshToken = redisTemplate.opsForValue().get("refresh_token:" + username);
if (storedRefreshToken != null && storedRefreshToken.equals(refreshToken)) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
String newAccessToken = jwtUtil.generateAccessToken(userDetails);
String newRefreshToken = jwtUtil.generateRefreshToken(userDetails);
// 更新响应头,返回新Token
response.setHeader("New-Access-Token", "Bearer " + newAccessToken);
response.setHeader("New-Refresh-Token", newRefreshToken);
// 用新Token继续请求
request.setAttribute("newAccessToken", newAccessToken);
}
}
}
}
filterChain.doFilter(request, response);
}
}
进阶优化:无感刷新与安全性保障
为了进一步提升用户体验与安全性,鳄鱼java社区推荐以下优化措施:
1. 前端静默刷新:前端拦截器在每次请求前解析Access Token的过期时间,若剩余时间小于5分钟,则调用刷新接口获取新Token,无需等待Token过期后再刷新,避免用户操作中断;
2. Refresh Token单次有效:每次用Refresh Token换取新Token时,删除Redis中旧的Refresh Token,生成新的Refresh Token并存储,防止Refresh Token泄露后被多次使用;
3. 使用RS256非对称加密算法:替代HS256对称加密,服务器用私钥签名Token,客户端用公钥验证签名,避免密钥泄露后Token被伪造;
4. Refresh Token的过期时间管控:Refresh Token的过期时间不宜过长(建议7-14天),同时可以在用户主动退出
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。





