Java Spring安全性-注销期间的并发请求
我们在web应用程序中使用Spring安全性。大多数页面都是安全的,即用户必须登录才能访问这些页面。它通常很好用。但是,我们在注销期间遇到了不需要的行为 假设用户登录并向服务器发送请求以加载某个(安全的)页面。在该请求完成之前,同一用户发送一个注销请求(即带有servlet\u路径“/j\u spring\u security\u logout”的请求)。注销请求通常非常快,可以比前一个请求更早完成。当然,注销请求会清除安全上下文。因此,前者的请求在生命周期的中间丢失安全上下文,这通常会引起异常。 事实上,用户不需要“手动”启动第一个请求。这种情况可能发生在具有自动刷新的页面上,即用户在自动发送刷新后仅几秒钟内按下注销链接 从一个角度来看,这可以被认为是一种有意义的行为。另一方面,我更希望在请求的生命周期中防止这样的安全上下文丢失。 有没有办法配置Spring安全性来避免这种情况?(类似于“当来自同一会话的其他并发请求存在时,推迟安全上下文的清除”或“在单个请求期间仅读取一次安全上下文,并将其缓存以供进一步使用”) 谢谢。所以这一切(毫不奇怪)都是出于设计。这些很好地解释了正在发生的事情——引用: 在单个会话中接收并发请求的应用程序中,相同的Java Spring安全性-注销期间的并发请求,java,spring-security,concurrency,logout,Java,Spring Security,Concurrency,Logout,我们在web应用程序中使用Spring安全性。大多数页面都是安全的,即用户必须登录才能访问这些页面。它通常很好用。但是,我们在注销期间遇到了不需要的行为 假设用户登录并向服务器发送请求以加载某个(安全的)页面。在该请求完成之前,同一用户发送一个注销请求(即带有servlet\u路径“/j\u spring\u security\u logout”的请求)。注销请求通常非常快,可以比前一个请求更早完成。当然,注销请求会清除安全上下文。因此,前者的请求在生命周期的中间丢失安全上下文,这通常会引起异常
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