SpringSecurity Session并发过期后会重定向到 /login (入口点问题)问题的解决
问题描述
在 SpringSecurity 中,我想配置一个关于session并发的控制,于是我是这样配置的
1 |
|
上下文的配置我在此省略了
这里设置 maximumSessions 为 -1,表示不限制同一账号登录的客户端数
session过期后执行的逻辑是进入我自定义的类 expiredSessionStrategy() 中
因为我是构建的 rest 服务,所以我是返回的 http 状态码
1 | public class ExpiredSessionStrategyImpl implements SessionInformationExpiredStrategy { |
在这里,问题就来了
我测试的时候,把 -1 改成了 1,之后登录同一个用户,后面登录的用户会把前面一个已经登录的用户挤下线,就是说之前登录的那个用户的session 会过期
就是说他所在的页面再发送任何请求的话会收到我返回的 405 状态码
在这里是没问题的
问题就在发完一个请求后,在发一个请求,在浏览器的 network 上会看到发出的请求会被重定向的 /login 请求上
后续再发任何请求都会被重定向到 /login 上
问题思考
为什么会出现这样的情况呢?
为什么会第一个请求会收到405的状态码,后续的请求会被重定向到 /login 呢?
通过 debug 断点,我定位到过滤器的前置执行方法 beforeInvocation() 上
1 | protected InterceptorStatusToken beforeInvocation(Object object) { |
问题出在了 SecurityContextHolder.getContext().getAuthentication() == null
getAuthentication() 为 null,于是进入了credentialsNotFound(),抛出了 AuthenticationCredentialsNotFoundException 异常
确实,在控制台上也能看到抛出的异常信息
问题深入
AuthenticationCredentialsNotFoundException 是 AuthenticationException 异常的子类
不仅仅是 AuthenticationCredentialsNotFoundException 还有其他很多异常都是异常的子类
既然抛出了异常,猜测肯定是被某个处理器给处理了而且处理的默认机制是重定向到 /login
于是继续搜索 SpringSecurity 异常处理器
我找到的答案是 ExceptionTranslationFilter
ExceptionTranslationFilter 是Spring Security的核心filter之一,用来处理AuthenticationException和AccessDeniedException两种异常(由FilterSecurityInterceptor认证请求返回的异常)
ExceptionTranslationFilter 对异常的处理是通过这两个处理类实现的,处理规则很简单:
规则1. 如果异常是 AuthenticationException,使用 AuthenticationEntryPoint 处理
规则2. 如果异常是 AccessDeniedException 且用户是匿名用户,使用 AuthenticationEntryPoint 处理
规则3. 如果异常是 AccessDeniedException 且用户不是匿名用户,如果否则交给 AccessDeniedHandler 处理。
1 | private void handleSpringSecurityException(HttpServletRequest request, |
我们这里的异常是 AuthenticationException ,紧接着就找 sendStartAuthentication() 方法
1 | protected void sendStartAuthentication(HttpServletRequest request, |
上面的方法是先保存请求,之后执行 authenticationEntryPoint.commence(request, response, reason), 再深入来看
默认实现 commence 接口的是 LoginUrlAuthenticationEntryPoint 类
1 | public void commence(HttpServletRequest request, HttpServletResponse response, |
我们看到了 redirectUrl = buildRedirectUrlToLoginPage(request, response, authException)
这下总算是知道了为什么会重定向了 /login 请求了
问题解决
知道问题的原因了,解决问题就很简单了,重新实现 commence 接口,返回http 状态码就可以了,于是加上这样的配置
1 |
|
1 | public class UnauthenticatedEntryPoint implements AuthenticationEntryPoint { |
再次重试,发现会返回 405状态码了,不会在重定向到 /login 了
问题解决