简体   繁体   English

在Spring Security中手动过期(无效)后,会话不会被销毁

[英]Session isn't destroyed just after being expired (invalidated) manually in Spring Security

I am writing code of a game (web application) where I want to implement the following strategy. 我正在编写游戏代码(Web应用程序),我想在其中实现以下策略。 The user is allowed to login once. 允许用户登录一次。 The second login results in warning message and a choice: to leave or to insist on logging in. If the user opts for logging in then all his previous sessions are to be expired. 第二次登录会产生警告消息并选择:离开或坚持登录。如果用户选择登录,则他之前的所有会话都将过期。 It works well thanks to Spring Security facilities: 由于Spring Security工具,它运行良好:

public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { ... .formLogin() .failureHandler(new SecurityErrorHandler()) .and() .sessionManagement() .maximumSessions(1) .maxSessionsPreventsLogin(true) ...

In case of the second login because of maxSessionsPreventsLogin(true) an exception is thrown. 如果由于maxSessionsPreventsLogin(true)而第二次登录,则抛出异常。 Then SecurityErrorHandler catches it and exercises redirection to the warning page: 然后SecurityErrorHandler捕获它并执行重定向到警告页面:

public class SecurityErrorHandler extends SimpleUrlAuthenticationFailureHandler {

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    if (exception.getClass().isAssignableFrom(SessionAuthenticationException.class)) {
        request.getRequestDispatcher("/double_login/"+request.getParameterValues("username")[0]).forward(request, response);   //...the rest of the method

Until now everything is ok. 到现在为止一切正常。 If despite of warning the user choices to login the second time (he has probably closed browser without logging out with the first session going on unmanageably or so) he presses a button and then the controller calls the special service for making the previous sessions expired for this user (using his logged in username): 如果尽管有警告,用户选择第二次登录(他可能关闭了浏览器而没有注销第一个会话无法管理的话),他按下一个按钮,然后控制器调用特殊服务使之前的会话过期此用户(使用他登录的用户名):

 public void expireUserSessions(String username) {
        for (Object principal : sessionRegistry.getAllPrincipals()) {
            if (principal instanceof User) {
                UserDetails userDetails = (UserDetails) principal;
                if (userDetails.getUsername().equals(username)) {
                    for (SessionInformation information : sessionRegistry.getAllSessions(userDetails, true)) {
                        information.expireNow();
                    }
                }
            }
        }
}

Moreover there is a SessionEventListener which (it doesn't matter because of logout, or natural expiring, or forced expiring by 'information.expireNow()') observes a session destroying event and implements a specific logic, such as saving user's persistent data, clean caches and so). 此外,还有一个SessionEventListener(由于注销,自然过期或由'information.expireNow()'强制到期而无关紧要)观察会话销毁事件并实现特定逻辑,例如保存用户的持久数据,干净的缓存等等)。 This logic IS CRITICAL. 这个逻辑非常重要。 The code which does it is the following: 执行此操作的代码如下:

public class SessionEventListener extends HttpSessionEventPublisher {

@Override
public void sessionCreated(HttpSessionEvent event) {
    super.sessionCreated(event);
    event.getSession().setMaxInactiveInterval(60*3);
}

@Override
public void sessionDestroyed(HttpSessionEvent event) {
    String name=null;
    SessionRegistry sessionRegistry = getSessionRegistry(event);
    SessionInformation sessionInfo = (sessionRegistry != null ? sessionRegistry
            .getSessionInformation(event.getSession().getId()) : null);
    UserDetails ud = null;
    if (sessionInfo != null) {
        ud = (UserDetails) sessionInfo.getPrincipal();
    }
    if (ud != null) {
        name=ud.getUsername();
        //OUR IMPORTANT ACTIONS IN CASE OF SESSION CLOSING 
        getAllGames(event).removeByName(name);
    }
    super.sessionDestroyed(event);
}



public AllGames getAllGames(HttpSessionEvent event){
    HttpSession session = event.getSession();
    ApplicationContext ctx =
            WebApplicationContextUtils.
                    getWebApplicationContext(session.getServletContext());
    return (AllGames) ctx.getBean("allGames");
}

public SessionRegistry getSessionRegistry(HttpSessionEvent event){
    HttpSession session = event.getSession();
    ApplicationContext ctx =
            WebApplicationContextUtils.
                    getWebApplicationContext(session.getServletContext());
    return (SessionRegistry) ctx.getBean("sessionRegistry");
}

} }

Then the hell happens. 然后地狱发生了。 Despite my expectations the event for 'sessionDestroyed' method occurs not immediately after session expiring but ONLY AFTER THE USER LOGS IN FOR THE SECOND TIME (it is allowed as his previous session is expired by that moment, but to my surprise, this previous session isn't destroyed by Spring Security until now). 尽管我的期望,'sessionDestroyed'方法的事件不是在会话到期后立即发生,而是仅在用户登录第二次之后(由于他之前的会话在那个时刻到期而被允许,但令我惊讶的是,此前一次会议不是直到现在才被Spring Security摧毁。 So the logic implemented in the service 'getAllGames(event).removeByName(name)', which is called from 'sessionDestroyed', happens too late and, what is worse, after the user logs in for the second time. 因此,在服务'getAllGames(event).removeByName(name)'中实现的逻辑(从'sessionDestroyed'调用)发生得太晚了,更糟糕的是,在用户第二次登录之后。 It breaks the logic. 它打破了逻辑。

I can implement different workarounds and so to say crutches. 我可以实现不同的解决方法,所以说拐杖。 But please, if somebody know how to solve it directly, I would like you to advice me. 但是,如果有人知道如何直接解决它,我希望你能给我一些建议。

Remarks. 备注。 I have called 'session.invalidate()' but it was also of no avail. 我打电话给'session.invalidate()',但也无济于事。

It is important for the already embodied logic that 'sessionDestroyed' in the SessionEventListener is triggered timely (immediately after session is expired). 对于已经体现的逻辑,重要的是SessionEventListener中的'sessionDestroyed'被及时触发(会话过期后立即)。 And frankly I don't know how to make it happens in the right and straightforward way. 坦率地说,我不知道如何以正确和直接的方式实现它。

I would appreciate your help and advice. 非常感谢您的帮助和建议。

The only answer I'v found by myself was the following. 我自己发现的唯一答案如下。 An expired session is still not destroyed until the next HTTP request is sent within this session. 在此会话中发送下一个HTTP请求之前,仍未销毁过期的会话。 Thus to kill an expired session you need to simulate such a request on behalf of the session. 因此,要终止过期的会话,您需要代表会话模拟此类请求。 I have done it by creating and calling the following method: 我通过创建并调用以下方法来完成它:

 void killExpiredSessionForSure(String sessionID) {

            //sessionID - belongs to a session you want to kill
            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.add("Cookie", "JSESSIONID=" + sessionID);
            HttpEntity requestEntity = new HttpEntity(null, requestHeaders);
            RestTemplate rt = new RestTemplate();
            rt.exchange("http://localhost:8080", HttpMethod.GET, requestEntity, String.class);          
 }

After this method is called the event 'sessionDestroyed' is properly issued and handled, and an expired session no longer exists. 调用此方法后,会正确发出和处理事件'sessionDestroyed',并且不再存在过期的会话。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM