Java 会话isn';t在Spring Security中手动过期(失效)后立即销毁
我正在编写一个游戏(web应用程序)的代码,我想在其中实现以下策略。允许用户登录一次。第二次登录会导致警告消息和选择:离开或坚持登录。如果用户选择登录,则其以前的所有会话都将过期。得益于Spring安全设施,它运行良好:Java 会话isn';t在Spring Security中手动过期(失效)后立即销毁,java,spring,spring-security,Java,Spring,Spring Security,我正在编写一个游戏(web应用程序)的代码,我想在其中实现以下策略。允许用户登录一次。第二次登录会导致警告消息和选择:离开或坚持登录。如果用户选择登录,则其以前的所有会话都将过期。得益于Spring安全设施,它运行良好: 公共类安全配置扩展了WebSecurity配置适配器{ @凌驾 受保护的无效配置(HttpSecurity http)引发异常{ ... .formLogin() .failureHandler(新的SecurityErrorHandler()) .及() .会议管理() .最
公共类安全配置扩展了WebSecurity配置适配器{
@凌驾
受保护的无效配置(HttpSecurity http)引发异常{
...
.formLogin()
.failureHandler(新的SecurityErrorHandler())
.及()
.会议管理()
.最多会议(1)
.maxSessionsPreventsLogin(真)
…
如果由于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
到目前为止,一切正常。如果尽管警告用户选择第二次登录(他可能在第一次会话无法管理的情况下关闭了浏览器而没有注销),他按下一个按钮,然后控制器调用特殊服务,以使该用户的前一次会话过期(使用他的登录用户名):
此外,还有一个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”被及时触发(会话到期后立即触发),这对于已经体现的逻辑非常重要。坦白说,我不知道如何以正确和直接的方式实现它
非常感谢您的帮助和建议。我自己找到的唯一答案是:在会话中发送下一个HTTP请求之前,过期会话仍然不会被销毁。因此,要终止过期会话,您需要代表会话模拟此类请求。我通过创建并调用followin来完成此操作g方法:
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”,并且过期的会话不再存在
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);
}