并发请求下SecurityContextHolder返回错误用户上下文问题排查
并发请求下SecurityContextHolder返回相同用户对象问题排查
问题现象
多并发请求到达控制器时,即便请求携带的身份头(PRE-AUTH)不同,SecurityContextHolder.getContext().getAuthentication().getPrincipal()偶尔会返回相同的用户对象。
相关代码
控制器代码
@RequestMapping(value = {"/users/{userId}/solveDetail/create"}, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @Transactional public ResponseEntity<CreateSolveDetail> createSolve(@PathVariable("userId") Long userId, @RequestBody CreateSolveDetail createSolveDetail){ User user =SecurityContextHolder.getContext().getAuthentication().getPrincipal(); }
WebSecurityConfig配置类
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityBasicConfig { @Autowired private Http403ForbiddenEntryPoint http403ForbiddenEntryPoint; @Bean public Http403ForbiddenEntryPoint http403ForbiddenEntryPoint() { return new Http403ForbiddenEntryPoint(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .exceptionHandling() .authenticationEntryPoint(http403ForbiddenEntryPoint) .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilterBefore(preAuthFilter(), BasicAuthenticationFilter.class); httpSecurity.csrf().disable(); SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL); } }
自定义PreAuthFilter过滤器
public class PreAuthFilter extends AbstractPreAuthenticatedProcessingFilter { @Override protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpServletRequest) { String auth = httpServletRequest.getHeader("PRE-AUTH"); try { User user = new ObjectMapper().readValue(auth, User.class); return user; } catch (Exception e) { return new User(); } } @Override protected Object getPreAuthenticatedCredentials(HttpServletRequest httpServletRequest) { String auth = httpServletRequest.getHeader("PRE-AUTH"); return auth; } }
已尝试无效方案
- 配置会话管理为
SessionCreationPolicy.STATELESS - 设置
SecurityContextHolder策略为MODE_THREADLOCAL
问题根源及解决方法
过滤器未清理线程上下文
容器线程池复用场景下,若未清理SecurityContext,后续请求会复用之前请求残留的上下文。修改自定义过滤器,在请求处理完成后强制清理:@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { super.doFilter(request, response, chain); } finally { SecurityContextHolder.clearContext(); } }SecurityContext策略设置时机错误
在configure方法中设置策略仅初始化时执行一次,需在类加载阶段提前设置,确保Spring上下文初始化前生效:static { SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL); }检查User类线程安全性
若User类存在共享可变字段,并发场景下会出现对象状态混乱。确保User类字段为不可变类型,或添加线程安全保护。确认过滤器注册方式
检查preAuthFilter()是否在配置类中标记为@Bean,避免单例过滤器的状态共享问题,确保每次请求使用无状态的过滤器实例。
环境信息
- Spring Boot版本:2.1.6.RELEASE
- 架构:微服务
内容的提问来源于stack exchange,提问作者utsav anand
相关产品推荐
相关产品推荐

