Spring security Spring Security中的联机/脱机状态

Spring security Spring Security中的联机/脱机状态,spring-security,Spring Security,在我的web应用程序中,我需要知道用户是在线还是离线。每当创建或销毁用户会话时,我都需要更新数据库中的状态,因此SessionRegistry不是一个选项 我使用会话计数器实现了它——当用户登录时,计数器递增,当用户注销或会话超时时,计数器递减 web.xml <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</liste

在我的web应用程序中,我需要知道用户是在线还是离线。每当创建或销毁用户会话时,我都需要更新数据库中的状态,因此SessionRegistry不是一个选项

我使用会话计数器实现了它——当用户登录时,计数器递增,当用户注销或会话超时时,计数器递减

web.xml

<listener>
    <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>

org.springframework.security.web.session.HttpSessionEventPublisher
服务

@Service
public class UsersLoginLogoutListener implements ApplicationListener<ApplicationEvent> {

    @Autowired
    private AccountService accountService;

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof InteractiveAuthenticationSuccessEvent) { // User log in
            System.out.println("User logged in...");
            InteractiveAuthenticationSuccessEvent iasEvent = (InteractiveAuthenticationSuccessEvent) event;
            String userName = iasEvent.getAuthentication().getName();
            if (userName != null) {
                System.out.println("...username is " + userName + ".");
                accountService.incrementSessionCounter(userName);
            }
        }
        else if (event instanceof SessionDestroyedEvent) { // User log out / session timeout
            System.out.println("User logged out or session timeout...");
            SessionDestroyedEvent sdEvent = (SessionDestroyedEvent) event;
            List<SecurityContext> lstSecurityContext = sdEvent.getSecurityContexts();
            System.out.println("...SecurityContexts list size: " + lstSecurityContext.size() + "...");
            for (SecurityContext securityContext : lstSecurityContext)
            {
                String userName = securityContext.getAuthentication().getName();
                if (userName != null) {
                    System.out.println("...username is " + userName + ".");
                    accountService.decrementSessionCounter(userName);
                }
            }
        }
    }
}
@服务
公共类UsersLoginLogoutListener实现ApplicationListener{
@自动连线
私人帐户服务;
ApplicationEvent上的公共无效(ApplicationEvent事件){
如果(InteractiveAuthenticationSuccessEvent的事件实例){//用户登录
System.out.println(“用户登录…”);
InteractiveAuthenticationSuccessEvent iasEvent=(InteractiveAuthenticationSuccessEvent)事件;
字符串userName=iasEvent.getAuthentication().getName();
如果(用户名!=null){
System.out.println(“…用户名为“+username+”);
accountService.incrementSessionCounter(用户名);
}
}
else if(SessionDestroyedEvent的事件实例){//用户注销/会话超时
System.out.println(“用户注销或会话超时…”);
SessionDestroyedEvent sdEvent=(SessionDestroyedEvent)事件;
List lstSecurityContext=sdEvent.getsecuritycontext();
System.out.println(“…SecurityContext列表大小:“+lstSecurityContext.size()+”);
用于(SecurityContext SecurityContext:lstSecurityContext)
{
字符串userName=securityContext.getAuthentication().getName();
如果(用户名!=null){
System.out.println(“…用户名为“+username+”);
accountService.decrementSessionCounter(用户名);
}
}
}
}
}
它一直工作得很好,直到最近,当我注意到一个计数器递增,但从未递减。多亏了这些日志,我才得以重现

用户在两个浏览器选项卡中打开登录页面,并在一个选项卡上登录,然后在另一个选项卡上登录,而不在第一个选项卡上注销。如果他输入了正确的凭据,一切正常:

用户已登录。。。用户名是testuser

用户注销或会话超时。。。SecurityContext列表大小: 1.用户名是testuser

用户已登录。。。用户名是testuser

尽管他使用同一个浏览器登录了两次,但他的第一次会话被破坏,我的计数器工作正常。 但如果他输入了错误的凭据,我会看到以下输出:

用户已登录。。。用户名是testuser

(会话超时后)

用户注销或会话超时。。。SecurityContext列表大小: 0


如您所见,在本例中,SessionDestroyeEvent被激发,但仅在会话超时和SecurityContext列表为空之后,因此用户的会话计数器没有减少。

您应该将其与
会话注册表
相结合,这将跟踪会话和属于会话的主体。这样,您就不必依赖会话来包含所需的信息。@M.Deinum我不确定,但这似乎是一个非常复杂的解决方案。我认为应该有一种更简单的方法来完成这样一项简单的任务。如果会话被破坏,那么数据仍然可用,因此您需要另一种方法来检索该信息。因此使用了
SessionRegistry
。为什么这么复杂?事实上,数据总是可用的,除了我描述的这个非常非标准的案例。也许这只是Spring Security中针对这个特定案例的一个bug。如果没有特殊的配置,SessionRegistry很难工作,例如。我查看了Spring安全源代码,发现所有这些东西都会增加很多不必要的开销。无论如何,如果您知道如何将所有这些结合起来,使在线/离线状态在没有复杂配置的情况下正常工作,我将非常感谢您的分享。很抱歉,我仍然看不到3行附加xml和将
@Autowired
字段添加到
ApplicationListener
的复杂之处。看见