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

Spring Boot(2.6.7)应用中追踪用户登录登出的最佳方式咨询

Spring Boot追踪用户登录/登出行为的最佳方案

针对你的场景(无登录/登出页面、部分调用方不复用会话、会话过期异常),推荐以下几种落地性强的方案:

一、利用Spring Security认证事件监听(优先推荐)

如果你的应用基于Spring Security实现认证,直接监听认证相关事件是最精准的方式:

  1. 登录追踪:监听AuthenticationSuccessEvent
    每当用户认证成功(不管是通过接口、Token还是其他方式),这个事件都会触发,直接在事件处理器里记录登录行为即可,无需依赖会话创建。

    示例代码:

    @Component
    public class AuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        @Override
        public void onApplicationEvent(AuthenticationSuccessEvent event) {
            Authentication auth = event.getAuthentication();
            String username = auth.getName();
            // 记录登录日志:用户名、登录时间、客户端信息等
            logger.info("用户 {} 登录成功", username);
        }
    }
    
  2. 登出追踪:结合LogoutSuccessEvent和SessionDestroyedEvent

    • 主动登出:如果有主动触发登出的接口,调用Spring Security的LogoutHandler会触发LogoutSuccessEvent,监听此事件记录主动登出。
    • 被动登出(会话过期/销毁):监听SessionDestroyedEvent,从事件中获取用户信息,再通过SessionRegistry检查该用户是否还有其他活跃会话,没有则记录登出。

    示例代码:

    @Component
    public class SessionDestroyedListener implements ApplicationListener<SessionDestroyedEvent> {
        @Autowired
        private SessionRegistry sessionRegistry;
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        @Override
        public void onApplicationEvent(SessionDestroyedEvent event) {
            List<SecurityContext> contexts = event.getSecurityContexts();
            for (SecurityContext context : contexts) {
                Authentication auth = context.getAuthentication();
                if (auth != null && auth.isAuthenticated()) {
                    String username = auth.getName();
                    // 检查用户是否还有其他活跃会话
                    for (Object principal : sessionRegistry.getAllPrincipals()) {
                        if (principal instanceof UserDetails && ((UserDetails) principal).getUsername().equals(username)) {
                            List<SessionInformation> sessions = sessionRegistry.getAllSessions(principal, false);
                            // 除了当前销毁的会话,还有其他活跃会话则不记录登出
                            if (sessions.size() <= 1) {
                                logger.info("用户 {} 所有会话已销毁,触发被动登出", username);
                            }
                            break;
                        }
                    }
                }
            }
        }
    }
    

二、自定义会话监听器(无Spring Security场景)

如果你的应用未使用Spring Security,可直接实现HttpSessionListener来追踪会话关联的用户行为:

  1. 登录时机:在用户完成身份校验后,将用户信息存入HttpSession,此时sessionCreated事件触发时,可判断会话是否关联了有效用户,从而记录登录。
  2. 登出时机:sessionDestroyed事件触发时,读取会话中的用户信息,再通过全局会话存储(比如自定义的Map或Redis)检查该用户是否还有其他活跃会话,无则记录登出。

示例代码:

@Component
public class CustomSessionListener implements HttpSessionListener {
    // 用ConcurrentHashMap存储用户活跃会话数,生产环境建议用Redis
    private static final Map<String, Integer> USER_SESSION_COUNT = new ConcurrentHashMap<>();
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        UserInfo user = (UserInfo) session.getAttribute("currentUser");
        if (user != null) {
            USER_SESSION_COUNT.put(user.getUsername(), USER_SESSION_COUNT.getOrDefault(user.getUsername(), 0) + 1);
            logger.info("用户 {} 创建新会话,当前活跃会话数:{}", user.getUsername(), USER_SESSION_COUNT.get(user.getUsername()));
        }
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        UserInfo user = (UserInfo) session.getAttribute("currentUser");
        if (user != null) {
            int count = USER_SESSION_COUNT.getOrDefault(user.getUsername(), 0) - 1;
            if (count <= 0) {
                USER_SESSION_COUNT.remove(user.getUsername());
                logger.info("用户 {} 所有会话已销毁,触发登出", user.getUsername());
            } else {
                USER_SESSION_COUNT.put(user.getUsername(), count);
                logger.info("用户 {} 销毁一个会话,当前活跃会话数:{}", user.getUsername(), count);
            }
        }
    }
}

三、结合SessionRegistry与定时任务(解决会话过期异常场景)

针对你当前会话无法自动过期的问题,可通过定时任务主动扫描SessionRegistry中的会话,判断是否过期并触发登出记录:

  1. 配置定时任务,定期检查所有会话的最后访问时间
  2. 对超过超时时间的会话,手动标记为过期并移除
  3. 检查用户剩余活跃会话,无则记录登出

示例代码:

@Component
public class SessionTimeoutChecker {
    @Autowired
    private SessionRegistry sessionRegistry;
    // 从配置文件读取会话超时时间,单位秒
    @Value("${server.servlet.session.timeout:1800}")
    private int sessionTimeout;
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Scheduled(fixedRate = 60000) // 每分钟扫描一次
    public void checkExpiredSessions() {
        List<Object> principals = sessionRegistry.getAllPrincipals();
        for (Object principal : principals) {
            List<SessionInformation> sessions = sessionRegistry.getAllSessions(principal, false);
            for (SessionInformation sessionInfo : sessions) {
                long lastAccessedTime = sessionInfo.getLastRequest().getTime();
                long currentTime = System.currentTimeMillis();
                if (currentTime - lastAccessedTime > sessionTimeout * 1000) {
                    sessionInfo.expireNow();
                    sessionRegistry.removeSessionInformation(sessionInfo.getSessionId());
                    // 检查用户是否还有其他活跃会话
                    List<SessionInformation> remainingSessions = sessionRegistry.getAllSessions(principal, false);
                    if (remainingSessions.isEmpty()) {
                        String username = ((UserDetails) principal).getUsername();
                        logger.info("用户 {} 会话已过期,所有会话销毁,触发登出", username);
                    }
                }
            }
        }
    }
}

前置注意事项

无论采用哪种方案,先解决会话无法过期的问题是核心前提:

  • 检查application.properties中server.servlet.session.timeout配置是否生效(注意单位:默认秒,若设置为30m则是30分钟)
  • 确认Jetty的会话配置未覆盖Spring Boot的设置,比如是否自定义了SessionManager导致过期逻辑失效
  • 若使用分布式会话(如Redis),检查会话存储的过期时间配置是否正确

内容的提问来源于stack exchange,提问作者doc

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 00:27:03