Spring Security - 注销期间的并发请求

Cim*_*man 11 java concurrency spring-security logout

我们在Web应用程序中使用Spring Security.大多数页面都是安全的,即用户必须登录才能访问这些页面.它通常工作正常.但是,我们在注销期间遇到了不需要的行为.

假设用户已登录并向服务器发送请求以加载某个(安全)页面.在此请求完成之前,同一用户发送注销请求(即使用servlet_path"/ j_spring_security_logout"的请求).注销请求通常非常快,并且可以比之前的请求更早地完成.当然,注销请求会清除安全上下文.因此,前一个请求在其生命中间丢失了安全上下文,这通常会导致异常.

实际上,用户无需"手动"启动第一个请求.这种情况可能发生在具有自动刷新的页面上,即用户在自动发送刷新后按下注销链接只需几分之一秒.

从一个角度来看,这可以被认为是一种有意义的行为.另一方面,我更愿意在请求的生命中期防止这种安全上下文丢失.

有没有办法配置Spring Security来避免这种情况?(类似于"当存在来自同一会话的其他并发请求时推迟清除安全上下文"或"在单个请求期间仅读取一次安全上下文并将其缓存以供进一步使用")

谢谢.

jlb*_*jlb 14

所以这一切(不足为奇)都是设计的.这些春季安全文档给出了正在发生的事情的一个很好的解释 - 引用:

在单个会话中接收并发请求的应用程序中,SecurityContext将在线程之间共享相同的实例.即使ThreadLocal正在使用a,它也是从HttpSession每个线程检索的相同实例.如果您希望临时更改运行线程的上下文,则会产生影响.如果您只是使用SecurityContextHolder.getContext()并调用setAuthentication(anAuthentication)返回的上下文对象,那么该Authentication对象将在共享同一SecurityContext实例的所有并发线程中更改.您可以自定义为每个请求SecurityContextPersistenceFilter创建全新的行为SecurityContext,防止一个线程中的更改影响另一个线程.或者,您可以在临时更改上下文的位置创建新实例.该方法SecurityContextHolder.createEmptyContext()始终返回新的上下文实例.

以上引用的片段说:

...你可以自定义SpringContextPersistenceFilter...... 的行为

不幸的是,文档没有提供任何有关如何执行此操作或甚至如何处理它的信息.这个问题问题(基本上是这个问题的提炼版本),但它没有受到太多关注.

还有这个SO答案提供了更深入的内部工作HttpSessionSecurityContextRepository,这可能是为了解决这个问题需要重新编写/更新的部分.

如果我通过一种很好的方式解决这个问题(比如在实现中创建一个新的上下文实例),我会更新这个答案.

更新

我遇到的问题的根源与读取用户id属性HttpSession(在它被并发"注销"请求清除之后)有关.而不是实现我自己,SpringContextRepository我决定创建一个简单的过滤器,将当前保存Authentication到请求,然后从那里工作.

这是基本的过滤器:

public class SaveAuthToRequestFilter extends OncePerRequestFilter {

    public static final String REQUEST_ATTR = SaveAuthToRequestFilter.class.getCanonicalName();

    @Override
    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
            throws ServletException, IOException {

        final SecurityContext context = SecurityContextHolder.getContext();
        if (context != null) {
            request.setAttribute(REQUEST_ATTR, context.getAuthentication());
        }

        filterChain.doFilter(request, response);
    }

}
Run Code Online (Sandbox Code Playgroud)

SecurityContextPersistenceFilter通过在您WebSecurityConfigurerAdapterconfigure(final HttpSecurity http)方法中添加以下内容后必须添加.

http.addFilterAfter(new SaveAuthToRequestFilter(), SecurityContextPersistenceFilter.class)
Run Code Online (Sandbox Code Playgroud)

完成后,您可以AuthenticationHttpServletRequest(@Autowired或注入到控制器方法中)读取"当前"(每个线程/请求)并从那里开始工作.我认为这个解决方案仍然有待改进,但它是我能想到的最轻量级的选择.感谢@chimmi和@ sura2k的灵感.