Java Spring安全性-注销期间的并发请求

Java Spring安全性-注销期间的并发请求,java,spring-security,concurrency,logout,Java,Spring Security,Concurrency,Logout,我们在web应用程序中使用Spring安全性。大多数页面都是安全的,即用户必须登录才能访问这些页面。它通常很好用。但是,我们在注销期间遇到了不需要的行为 假设用户登录并向服务器发送请求以加载某个(安全的)页面。在该请求完成之前,同一用户发送一个注销请求(即带有servlet\u路径“/j\u spring\u security\u logout”的请求)。注销请求通常非常快,可以比前一个请求更早完成。当然,注销请求会清除安全上下文。因此,前者的请求在生命周期的中间丢失安全上下文,这通常会引起异常

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

假设用户登录并向服务器发送请求以加载某个(安全的)页面。在该请求完成之前,同一用户发送一个注销请求(即带有servlet\u路径“/j\u spring\u security\u logout”的请求)。注销请求通常非常快,可以比前一个请求更早完成。当然,注销请求会清除安全上下文。因此,前者的请求在生命周期的中间丢失安全上下文,这通常会引起异常。 事实上,用户不需要“手动”启动第一个请求。这种情况可能发生在具有自动刷新的页面上,即用户在自动发送刷新后仅几秒钟内按下注销链接

从一个角度来看,这可以被认为是一种有意义的行为。另一方面,我更希望在请求的生命周期中防止这样的安全上下文丢失。 有没有办法配置Spring安全性来避免这种情况?(类似于“当来自同一会话的其他并发请求存在时,推迟安全上下文的清除”或“在单个请求期间仅读取一次安全上下文,并将其缓存以供进一步使用”)

谢谢。

所以这一切(毫不奇怪)都是出于设计。这些很好地解释了正在发生的事情——引用:

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

上面引用的一段话说:

。。。您可以自定义
SpringContextPersistenceFilter
的行为

不幸的是,这些文件没有提供任何关于如何进行这项工作的信息,甚至没有提供如何进行这项工作的信息。这就是问题所在(本质上是这个问题的一个提炼版本),但它没有得到太多的关注

还有一点可以让您更深入地了解
HttpSessionSecurityContextRepository
的内部工作原理,这可能是需要重新编写/更新以解决此问题的部分

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

更新 我遇到的问题的根源与从
HttpSession
读取用户id属性有关(在通过并发“注销”请求清除该属性之后)。我没有实现自己的
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
之后添加,方法是将以下内容添加到
WebSecurityConfigureAdapter
配置(最终HttpSecurity http)
方法中

http.addFilterAfter(new SaveAuthToRequestFilter(), SecurityContextPersistenceFilter.class)
设置好后,您可以从
HttpServletRequest
@Autowired
或注入控制器方法)读取“当前”(每个线程/请求)
身份验证
,然后从那里开始工作。我认为这个解决方案还有待改进,但它是我能想到的最轻量级的选择。感谢@chimmi和@sura2k的灵感。

免责声明:相关行为是故意实施的。如果有人决定禁用它,他们应该阅读描述你得到的问题作为回报。


如果我理解正确,问题是注销会清除SecurityContext的
身份验证
数据,从而使

SecurityContextHolder.getContext().getAuthentication()
返回null。但注销并不会清除上下文本身,若您的第一个请求在注销发生之前成功捕获了上下文,那个么对于第一个请求,它将停留在ThreadLocal中

因此,我们所需要的不是清除
身份验证
,事实证明(因为Spring非常棒),负责这一点的
SecurityContextLogoutHandler
有一个属性:

private boolean clearAuthentication = true;
这正是我们所需要的:

如果为true,则从SecurityContext中删除身份验证以防止并发请求出现问题


您的需求在JIRA中被报告为一个bug(不是特性请求)。他们在Spring3.2中修复了它,所以您希望在这里发生/解决它,但它的设计会隐式地阻止它

默认行为是将
SecurityContext
保存在
HttpSession
中,这是afaik spring security目前提供的唯一实现

即使是
SecurityContext
也是
ThreadLocal
它也与
HttpSession
中的相同。因此,当清理
SecurityContext
时,它将删除fr