You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 10:31:20