如何在Spring Security(Java17注解式)实现自动登出并对接React前端?
针对你的闲置自动登出及token过期后无法删除的问题,给你几个落地的解决方案,适配Java 17注解式开发:
方案一:前端心跳+后端定时清理+会话状态校验
这个方案从根源解决token过期后无法触发删除的问题,同时实现闲置自动清理:
- 前端每隔闲置超时的1/3时长发送心跳请求(比如
/api/heartbeat),携带access token;后端验证token有效性后,更新数据库中该token的last_active_time字段 - 后端用
@Scheduled定时任务扫描数据库,直接删除last_active_time超出闲置阈值的token,同时清理已过期的token - 前端保留闲置定时器做双重保障,且每次请求接口收到401/403时直接跳转登录页
代码示例
后端心跳接口
@RestController @RequestMapping("/api") public class HeartbeatController { @Autowired private TokenRepository tokenRepository; @PostMapping("/heartbeat") public ResponseEntity<Void> heartbeat(@RequestHeader("Authorization") String authHeader) { String accessToken = authHeader.replace("Bearer ", ""); Optional<TokenEntity> tokenOpt = tokenRepository.findByAccessToken(accessToken); if (tokenOpt.isPresent()) { TokenEntity token = tokenOpt.get(); token.setLastActiveTime(LocalDateTime.now()); tokenRepository.save(token); return ResponseEntity.ok().build(); } return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } }
定时清理任务
@Component public class TokenCleanupScheduler { @Autowired private TokenRepository tokenRepository; // 每5分钟执行一次清理,可按需调整 @Scheduled(fixedRate = 300000) public void cleanupExpiredOrIdleTokens() { // 闲置超时设为30分钟,可配置到配置文件 LocalDateTime idleThreshold = LocalDateTime.now().minusMinutes(30); tokenRepository.deleteByLastActiveTimeBefore(idleThreshold); // 同时清理已过期的token LocalDateTime now = LocalDateTime.now(); tokenRepository.deleteByExpiryTimeBefore(now); } }
注意:需在启动类添加
@EnableScheduling开启定时任务支持
方案二:优化JWT验证逻辑,绑定数据库状态
让token的有效性完全由数据库控制,避免token过期后无法触发删除的尴尬:
- 给access token设置较短有效期(比如15分钟),refresh token设置较长有效期(比如7天)
- 后端所有接口验证token时,除了校验JWT签名和过期时间,必须额外查询数据库确认token存在且未被标记失效
- 后端定时清理闲置token后,即使前端拿着未过期的access token,请求也会被拦截(数据库无对应记录),前端收到401后直接跳转登录
代码示例(Spring Security拦截器)
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtTokenProvider jwtTokenProvider; @Autowired private TokenRepository tokenRepository; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = jwtTokenProvider.resolveToken(request); if (token != null && jwtTokenProvider.validateToken(token)) { // 额外校验数据库中token是否存在 if (!tokenRepository.existsByAccessToken(token)) { response.sendError(HttpStatus.UNAUTHORIZED.value(), "Token invalid or revoked"); return; } Authentication auth = jwtTokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(auth); } filterChain.doFilter(request, response); } }
方案三:后端通过WebSocket主动通知前端登出
这就是你询问的「后端删token后通知前端跳转」的实现方式,适合需要实时通知的场景:
- 后端集成WebSocket,用户登录后建立连接并绑定用户ID
- 后端定时清理闲置token时,通过WebSocket向对应用户发送登出通知
- 前端监听WebSocket消息,收到通知后直接跳转登录页,可额外调用登出API确认
代码示例
WebSocket配置
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); } }
定时清理+发送通知
@Component public class TokenCleanupScheduler { @Autowired private TokenRepository tokenRepository; @Autowired private SimpMessagingTemplate messagingTemplate; @Scheduled(fixedRate = 300000) public void cleanupIdleTokens() { LocalDateTime idleThreshold = LocalDateTime.now().minusMinutes(30); List<TokenEntity> idleTokens = tokenRepository.findByLastActiveTimeBefore(idleThreshold); for (TokenEntity token : idleTokens) { // 向用户专属topic发送登出通知 messagingTemplate.convertAndSend("/topic/logout/" + token.getUserId(), "Idle timeout, please login again"); tokenRepository.delete(token); } } }
前端React监听示例
import SockJS from 'sockjs-client'; import Stomp from 'stompjs'; export function setupWebSocket(userId) { const socket = new SockJS('/ws'); const stompClient = Stomp.over(socket); stompClient.connect({}, () => { stompClient.subscribe(`/topic/logout/${userId}`, () => { window.location.href = '/login'; }); }); return stompClient; }
方案选型建议
- 若不需要实时通知,方案一+方案二组合最稳妥,实现简单无额外依赖
- 若需要实时提升用户体验,方案三配合方案一使用,兼顾清理逻辑和实时通知
内容的提问来源于stack exchange,提问作者FunkyBuddha741
相关产品推荐
相关产品推荐

