Spring中自定义Filter无法获取UserDetails的问题求助
我明白你现在卡在哪了——你的TenantFilter总是抢在Spring Security的认证过滤器前面执行,导致SecurityContext还没被填充,根本拿不到用户的principal信息对吧?咱们来一步步把这个问题解决掉。
问题根源
你之前用FilterRegistrationBean注册过滤器的方式,本质是把TenantFilter放到了Spring Security过滤器链的外部。不管你设置多大的order值,外部过滤器都会优先于Spring Security的内置过滤器链执行,这就是为什么你在过滤器里拿不到认证信息,但控制器里却能拿到的原因——控制器是在整个过滤器链执行完之后才处理请求的。
解决方案:把过滤器整合到Spring Security链中
不要用FilterRegistrationBean注册你的租户过滤器,而是直接把它加到Spring Security的内置过滤器链里,指定它在认证完成之后执行。
步骤1:修改TenantFilter,去掉自动注册
先把@Component注解去掉,避免Spring自动把它注册成全局过滤器:
// 移除@Component注解,我们将通过Security配置手动注册 public class TenantFilter implements Filter { @Autowired private TenantStore tenantStore; @Autowired private UserService userService; @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; User user = null; // 增加空指针判断,避免NPE Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null && auth.getPrincipal() instanceof User) { user = (User) auth.getPrincipal(); } String tenantId = user != null ? user.getSchool().getCode() : ""; try { this.tenantStore.setTenantId(tenantId); chain.doFilter(servletRequest, servletResponse); } finally { // 必须清理ThreadLocal,避免线程复用导致的租户信息污染 this.tenantStore.clear(); } } @Override public void destroy() {} }
步骤2:修改Spring Security配置,添加租户过滤器
创建或修改你的Security配置类,用addFilterAfter方法把TenantFilter加到认证过滤器之后:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 这里放你的其他安全配置,比如授权规则、csrf设置等 .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) // 关键:把TenantFilter放到用户名密码认证过滤器之后执行 // 如果用的是JWT/OAuth2,换成对应的认证过滤器类,比如JwtAuthenticationTokenFilter .addFilterAfter(tenantFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } // 手动注册TenantFilter的Bean @Bean public TenantFilter tenantFilter() { return new TenantFilter(); } // 你的其他Security相关Bean,比如UserDetailsService、PasswordEncoder等 }
步骤3:精简TenantFilterConfig
现在不需要FilterRegistrationBean了,删掉相关配置,只保留租户存储的Bean定义:
@Configuration public class TenantFilterConfig { @Bean(destroyMethod = "destroy") public ThreadLocalTargetSource threadLocalTenantStore() { ThreadLocalTargetSource result = new ThreadLocalTargetSource(); result.setTargetBeanName("tenantStore"); return result; } @Primary @Bean(name = "proxiedThreadLocalTargetSource") public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) { ProxyFactoryBean result = new ProxyFactoryBean(); result.setTargetSource(threadLocalTargetSource); return result; } @Bean(name = "tenantStore") @Scope(scopeName = "prototype") public TenantStore tenantStore() { return new TenantStore(); } }
额外注意事项
- 如果你的项目用的是JWT、OAuth2等非表单登录的认证方式,一定要把
UsernamePasswordAuthenticationFilter.class换成对应的认证过滤器类,比如JwtAuthenticationTokenFilter或者OAuth2AuthenticationProcessingFilter,确保租户过滤器在认证完成后执行。 - 始终在
finally块中清理ThreadLocal里的租户信息,这能避免线程池复用导致的租户信息串用问题。
内容的提问来源于stack exchange,提问作者Jordan Mackie
相关产品推荐
相关产品推荐

