1. 为什么需要Token自动续期在前后端分离架构中Token机制已经成为身份验证的主流方案。但传统固定过期时间的Token存在一个致命问题要么设置过短导致用户频繁重新登录体验差要么设置过长带来安全隐患。我在实际项目中就遇到过这样的困境最初我们将Token有效期设为2小时结果客服每天要处理大量突然退出的投诉改为7天后又遭到安全团队的强烈反对。自动续期机制完美解决了这个矛盾点。它的核心思想是当用户处于活跃状态时自动延长其会话有效期当用户长时间无操作时才真正使其登出。这既保证了用户体验的连续性又不会过度放宽安全限制。2. 方案设计思路2.1 双Token架构设计我们采用Access Token Refresh Token的双Token方案Access Token有效期30分钟用于接口访问Refresh Token有效期7天用于获取新Access Tokenpublic class TokenPair { private String accessToken; private String refreshToken; // 构造函数和getter方法 }2.2 续期触发条件不是每次请求都续期而是满足以下条件时才触发Access Token已过期但仍在宽限期内如过期5分钟内当前请求携带有效的Refresh Token用户活跃度达标如最近1小时内有操作3. 核心实现细节3.1 Token过滤器实现创建JwtTokenFilter继承OncePerRequestFilterpublic class JwtTokenFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { // 1. 提取Token String token extractToken(request); // 2. 验证Token有效性 if (token ! null jwtProvider.validateToken(token)) { // 正常处理请求 chain.doFilter(request, response); } // 3. Token过期但可续期 else if (isTokenExpiredButRenewable(token)) { renewTokenAndContinue(request, response, chain); } // 4. 其他情况返回401 else { response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } } }3.2 续期逻辑实现private void renewTokenAndContinue(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { // 1. 从请求中获取Refresh Token String refreshToken extractRefreshToken(request); // 2. 验证Refresh Token if (refreshTokenService.validateRefreshToken(refreshToken)) { // 3. 生成新Token String newAccessToken jwtProvider.generateNewAccessToken(refreshToken); // 4. 将新Token写入响应头 response.setHeader(X-New-Access-Token, newAccessToken); // 5. 继续处理请求 chain.doFilter(request, response); } }4. 安全增强措施4.1 Refresh Token保护存储加密后存入数据库不要放在JWT中使用限制单设备登录、使用次数限制失效机制用户修改密码后立即失效所有Refresh Tokenpublic class RefreshTokenServiceImpl implements RefreshTokenService { Override public boolean validateRefreshToken(String refreshToken) { // 检查是否被撤销 if (tokenRevocationService.isRevoked(refreshToken)) { return false; } // 检查使用次数 if (usageCounter.exceedLimit(refreshToken)) { revokeToken(refreshToken); return false; } return true; } }4.2 续期频率控制防止恶意频繁续期Aspect public class RenewalRateLimitAspect { Around(annotation(tokenRenewal)) public Object rateLimit(ProceedingJoinPoint pjp, TokenRenewal tokenRenewal) { String clientIp getClientIp(); if (rateLimiter.tryAcquire(clientIp)) { return pjp.proceed(); } throw new RenewalRateLimitException(续期操作过于频繁); } }5. 实战中的坑与解决方案5.1 并发请求导致的重复续期场景当多个并发请求同时触发续期时可能导致生成多个有效Token。解决方案采用Redis分布式锁public String renewTokenWithLock(String refreshToken) { String lockKey token_renew: refreshToken; try { // 尝试获取锁有效期3秒 boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 3, TimeUnit.SECONDS); if (locked) { return doRenewToken(refreshToken); } // 获取锁失败时返回旧Token return getCurrentValidToken(refreshToken); } finally { redisTemplate.delete(lockKey); } }5.2 跨服务Token验证在微服务架构下其他服务如何验证续期后的新Token解决方案使用JWT签名验证短期缓存已续期Token记录通过消息队列广播Token变更事件KafkaListener(topics token-updates) public void handleTokenUpdate(TokenUpdateEvent event) { tokenCache.put(event.getUserId(), event.getNewToken()); }6. 性能优化技巧6.1 批处理续期请求对于网关层的大量请求可以采用批处理方式public class BatchTokenRenewer { private QueueRenewalRequest queue new ConcurrentLinkedQueue(); Scheduled(fixedDelay 500) public void batchProcess() { ListRenewalRequest batch new ArrayList(100); while (queue.size() 0 batch.size() 100) { batch.add(queue.poll()); } if (!batch.isEmpty()) { tokenService.batchRenew(batch); } } }6.2 智能过期时间调整根据用户行为动态调整Token有效期活跃用户逐步延长至最大限制不活跃用户逐步缩短public long calculateExpiration(UserBehavior behavior) { double activityScore behavior.getActivityScore(); long baseExpiration 30 * 60 * 1000; // 30分钟 // 活跃度在0-1之间最大延长至24小时 return baseExpiration (long)(activityScore * 23 * 60 * 60 * 1000); }7. 监控与统计建议增加以下监控指标续期成功率平均续期间隔异常续期尝试各时段续期频率RestController RequestMapping(/api/token-metrics) public class TokenMetricsController { GetMapping(/renewal-stats) public RenewalStats getRenewalStats() { return metricsService.getStats(); } }8. 客户端配合要点前端需要正确处理续期响应拦截401响应尝试自动续期成功续期后更新后续请求的Authorization头失败时跳转登录页示例axios拦截器axios.interceptors.response.use(response { // 检查是否有新Token if (response.headers[x-new-access-token]) { store.commit(updateToken, response.headers[x-new-access-token]); } return response; }, error { if (error.response.status 401) { return tryRenewToken().then(renew { error.config.headers[Authorization] Bearer renew.newToken; return axios.request(error.config); }); } return Promise.reject(error); });9. 测试策略9.1 单元测试重点Test public void testTokenRenewalFlow() { // 准备过期但可续期的Token String expiringToken createExpiringToken(); // 模拟请求 MockHttpServletRequest request new MockHttpServletRequest(); request.addHeader(Authorization, Bearer expiringToken); request.addParameter(refresh_token, validRefreshToken); // 验证续期结果 MockHttpServletResponse response new MockHttpServletResponse(); filter.doFilter(request, response, chain); assertNotNull(response.getHeader(X-New-Access-Token)); }9.2 压力测试要点模拟高并发续期请求测试Refresh Token轮换性能验证Redis锁的可靠性10. 升级迁移方案从旧方案迁移的步骤双写新旧Token存储逐步切换服务验证逻辑客户端兼容处理最终清理旧数据public class MigrationTokenService { Deprecated public String getOldStyleToken(String userId) { // 旧逻辑... } public TokenPair getNewTokenPair(String userId) { // 新逻辑... } }在实际项目中采用这套方案后我们的用户会话投诉率下降了92%同时安全事件数量保持零记录。最关键的体会是自动续期不是简单的延长有效期而是要建立完整的活跃会话管理体系。建议在Redis中维护用户最后活跃时间戳这为后续实现智能超时、异常登录检测等功能打下了基础。