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

标签 java spring-security concurrency logout

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

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

事实上,用户不需要“手动”启动第一个请求。这种情况可能发生在具有自动刷新功能的页面上,即用户在自动发送刷新后的几分之一秒内按下注销链接。

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

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

谢谢。

最佳答案

所以这一切(不出所料)都是设计使然。这些spring security docs对正在发生的事情给出一个很好的解释 - 引用:

In an application which receives concurrent requests in a single session, the same SecurityContext instance will be shared between threads. Even though a ThreadLocal is being used, it is the same instance that is retrieved from the HttpSession for each thread. This has implications if you wish to temporarily change the context under which a thread is running. If you just use SecurityContextHolder.getContext(), and call setAuthentication(anAuthentication) on the returned context object, then the Authentication object will change in all concurrent threads which share the same SecurityContext instance. You can customize the behaviour of SecurityContextPersistenceFilter to create a completely new SecurityContext for each request, preventing changes in one thread from affecting another. Alternatively you can create a new instance just at the point where you temporarily change the context. The method SecurityContextHolder.createEmptyContext() always returns a new context instance.

上面引述的一个片段说:

... you can customise the behaviour of SpringContextPersistenceFilter ...

不幸的是,文档没有提供任何关于如何去做这件事的信息,或者甚至是如何接近它的信息。这SO question问的正是这个问题(本质上是这个问题的提炼版本),但它并没有受到太多关注。

还有这个 SO answer这更深入地介绍了 HttpSessionSecurityContextRepository 的内部工作原理,这很可能是需要重写/更新以解决此问题的部分。

如果我在实现中找到解决这个问题的好方法(例如创建上下文的新实例),我会更新这个答案。

更新

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

这是基本过滤器:

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

}

必须在 SecurityContextPersistenceFilter 之后添加,方法是将以下内容添加到您的 WebSecurityConfigurerAdapterconfigure(final HttpSecurity http) 方法中。

http.addFilterAfter(new SaveAuthToRequestFilter(), SecurityContextPersistenceFilter.class)

完成后,您可以从 HttpServletRequest(@Autowired 或注入(inject)到您的 Controller 方法中)并从那里开始工作。我认为这个解决方案仍有不足之处,但它是我能想到的最轻量级的选择。感谢@chimmi 和@sura2k 的启发。

关于java - Spring Security - 注销期间的并发请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37595374/

相关文章:

asp.net - 高并发系统中的缓存访问

java - Spring @Transactional 防止开启事务

java - 使用 Spring Security 3 在我的 Grails OAuth 提供程序上存储访问 token

java - 如何自定义 IDP 登录页面

concurrency - Tesseract(OCR 引擎)是可重入的吗?

java - 从特定方法调用时,JFrame 中的内容不显示

java - 如何用 Java 执行简单的 Google Cloud Trace 请求

java - 通过 http 寻找 api/协议(protocol)

java - 如何在 mysql 或 java 中创建日期

Spring MVC Spring 安全性和错误处理