【问题标题】:Session isn't destroyed just after being expired (invalidated) manually in Spring Security在 Spring Security 中手动过期(无效)后,会话不会被销毁
【发布时间】:2018-06-14 12:53:13
【问题描述】:

我正在编写一个游戏(网络应用程序)的代码,我想在其中实现以下策略。允许用户登录一次。第二次登录导致警告消息和选择:离开或坚持登录。如果用户选择登录,那么他之前的所有会话都将过期。得益于 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) ...

如果由于 maxSessionsPreventsLogin(true) 而进行第二次登录,则会引发异常。然后 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

到目前为止一切正常。如果尽管警告用户选择第二次登录(他可能已经关闭了浏览器而没有注销而第一次会话无法管理左右),他按下一个按钮,然后控制器调用特殊服务以使先前的会话过期此用户(使用他的登录用户名):

 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();
                    }
                }
            }
        }
}

此外,还有一个 SessionEventListener (因为注销,自然过期,或通过 'information.expireNow()' 强制过期都没有关系)观察会话破坏事件并实现特定逻辑,例如保存用户的持久数据,干净的缓存等等)。这个逻辑很关键。执行此操作的代码如下:

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");
}

}

然后地狱发生了。尽管我期望“sessionDestroyed”方法的事件不会在会话到期后立即发生,而是仅在用户第二次登录后才发生(允许,因为他的上一个会话在那一刻过期,但令我惊讶的是,上一个会话是直到现在才被 Spring Security 破坏)。因此,从“sessionDestroyed”调用的服务“getAllGames(event).removeByName(name)”中实现的逻辑发生得太晚了,更糟糕的是,在用户第二次登录之后。它打破了逻辑。

我可以实施不同的解决方法,比如说拐杖。但是,如果有人知道如何直接解决它,我希望你给我建议。

备注。我已经调用了“session.invalidate()”,但它也无济于事。

对于已经体现的逻辑来说,及时触发 SessionEventListener 中的“sessionDestroyed”是很重要的(在会话过期后立即触发)。坦率地说,我不知道如何让它以正确和直接的方式发生。

感谢您的帮助和建议。

【问题讨论】:

    标签: java spring spring-security


    【解决方案1】:

    我自己找到的唯一答案如下。在此会话中发送下一个 HTTP 请求之前,过期的会话仍然不会被销毁。因此,要终止过期会话,您需要代表会话模拟这样的请求。我通过创建和调用以下方法来完成它:

     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);          
     }
    

    调用此方法后,“sessionDestroyed”事件被正确发出和处理,过期的会话不再存在。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-22
      • 2013-04-27
      • 2015-06-30
      • 1970-01-01
      • 2015-08-16
      • 2011-12-12
      相关资源
      最近更新 更多