Spring Security自定义认证过滤器与方法安全执行顺序问题
问题分析与解决方案
你的核心问题是方法安全拦截器(处理@PreAuthorize)在自定义认证过滤器之前执行,导致SecurityContext未填充用户身份,最终返回403。下面从配置问题、过滤器选型、修正步骤三个维度给出具体解决方案:
一、核心原因:过滤器链顺序配置错误
Spring Security的方法安全拦截器(MethodSecurityInterceptor)是在Spring MVC控制器方法执行前触发的,而你的自定义AbstractAuthenticationProcessingFilter没有被放置到过滤器链的前置位置,导致认证流程晚于权限检查。
另外,AbstractAuthenticationProcessingFilter本身是继承OncePerRequestFilter的,二者核心差异在于:
AbstractAuthenticationProcessingFilter默认只拦截特定路径(通过setFilterProcessesUrl指定),适合表单登录这类定向认证场景;OncePerRequestFilter会对每个请求执行一次,更适合令牌认证这种全局请求校验的场景。
二、具体修正步骤
1. 调整自定义过滤器的位置(关键)
无论使用哪种过滤器,都必须将其添加到Spring Security过滤器链的前端,确保在方法安全检查前完成认证。
示例1:使用AbstractAuthenticationProcessingFilter的正确配置
@Configuration @Order(2) // 受保护路径配置,优先级低于白名单 public class ProtectedSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomTokenAuthFilter customTokenAuthFilter; @Override protected void configure(HttpSecurity http) throws Exception { http .antMatcher("/**") // 处理除白名单外的所有请求 .authorizeRequests() .anyRequest().authenticated() .and() // 将自定义过滤器放到UsernamePasswordAuthenticationFilter之前 .addFilterBefore(customTokenAuthFilter, UsernamePasswordAuthenticationFilter.class) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 令牌认证建议无状态 } // 自定义过滤器实例化,需指定拦截路径 @Bean public CustomTokenAuthFilter customTokenAuthFilter() throws Exception { CustomTokenAuthFilter filter = new CustomTokenAuthFilter("/**"); // 拦截所有路径 filter.setAuthenticationManager(authenticationManagerBean()); // 设置认证成功/失败处理器 filter.setAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler()); filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler()); return filter; } }
示例2:改用OncePerRequestFilter(更适合令牌认证)
如果你的认证逻辑不需要针对特定路径触发,推荐使用OncePerRequestFilter,它能确保每个请求都先经过认证:
@Component public class CustomTokenAuthFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从请求头/参数中提取令牌 String token = request.getHeader("Authorization"); if (token != null && SecurityContextHolder.getContext().getAuthentication() == null) { // 验证令牌,生成Authentication对象 Authentication auth = validateTokenAndGetAuth(token); if (auth != null) { // 将认证信息存入SecurityContext SecurityContextHolder.getContext().setAuthentication(auth); } } filterChain.doFilter(request, response); } // 实现令牌验证逻辑,返回合法的Authentication对象 private Authentication validateTokenAndGetAuth(String token) { // 省略校验逻辑,示例返回带权限的认证对象 List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_SOMETHING"); return new UsernamePasswordAuthenticationToken("username", null, authorities); } }
然后在受保护路径配置中添加该过滤器:
@Configuration @Order(2) public class ProtectedSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomTokenAuthFilter customTokenAuthFilter; @Override protected void configure(HttpSecurity http) throws Exception { http .antMatcher("/**") .authorizeRequests() .anyRequest().authenticated() .and() .addFilterBefore(customTokenAuthFilter, UsernamePasswordAuthenticationFilter.class) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }
2. 确保白名单配置的独立性
@Order(1)的白名单配置要严格限制路径范围,避免干扰受保护路径的过滤器链:
@Configuration @Order(1) public class WhitelistSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .antMatcher("/public/**") // 仅处理白名单路径 .authorizeRequests() .anyRequest().permitAll() .and() .csrf().disable(); } }
3. 校验方法安全的角色层级配置
确保全局方法安全配置正确,角色层级生效:
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityExpressionHandler createExpressionHandler() { DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler(); handler.setRoleHierarchy(roleHierarchy()); return handler; } @Bean public RoleHierarchy roleHierarchy() { RoleHierarchyImpl hierarchy = new RoleHierarchyImpl(); hierarchy.setHierarchy("ROLE_ADMIN > ROLE_SOMETHING"); // 示例层级 return hierarchy; } }
三、关键注意事项
- 确保认证成功后,
Authentication对象被正确存入SecurityContextHolder(基于ThreadLocal,请求线程内有效); - 令牌认证场景建议启用
SessionCreationPolicy.STATELESS,避免会话干扰; - 检查用户的权限集合中是否确实包含
ROLE_SOMETHING(注意Spring Security默认会给角色添加ROLE_前缀,避免权限名称不匹配)。
内容的提问来源于stack exchange,提问作者Noobybooby
相关产品推荐
相关产品推荐

