You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何OncePerRequestFilter在permitAll()端点仍执行?Spring Security疑问

问题描述

在Spring Security配置中使用SecurityFilterChain,通过authorizeHttpRequests设置/user/register等端点无需认证(permitAll()),但通过addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)添加的自定义JWT过滤器仍会在这些公开端点执行,因无令牌导致抛出JWTDecodeException。

已通过在OncePerRequestFilter中重写shouldNotFilter()方法跳过这些路径解决问题,但存在疑问:原以为permitAll()会绕过所有认证相关处理,想明确:

  1. SecurityFilterChain中OncePerRequestFilter与authorizeHttpRequests的执行顺序
  2. addFilterBefore是否会无视authorizeHttpRequests规则始终执行过滤器
  3. 跳过公开端点过滤器的标准模式

相关配置代码:

@Bean
@Order(2)
public SecurityFilterChain userFilterChain(
    HttpSecurity http, @Qualifier("userAuthManager") AuthenticationManager userAuthManager)
    throws Exception {

  return http.securityMatcher("/user/**")
      .csrf(AbstractHttpConfigurer::disable)
      .httpBasic(AbstractHttpConfigurer::disable)
      .cors(Customizer.withDefaults())
      .authorizeHttpRequests(
          auth ->
              auth.requestMatchers("/user/me", "/user/update/**", "/user/logout")
                  .authenticated()
                  .anyRequest()
                  .permitAll())
      .sessionManagement(
          session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
      .authenticationManager(userAuthManager)
      .addFilterBefore(userJWTFilter, UsernamePasswordAuthenticationFilter.class)
      .build();
}
@RestController
@RequestMapping("/user")
public class AuthController {
  ...

  @PostMapping("/register")
  public ResponseEntity<?> registerUser(
      ...
  }

  ...
}

核心解答

1. 执行顺序:过滤器链先于授权规则

Spring Security的请求处理流程是过滤器链优先执行,授权决策(authorizeHttpRequests)后置处理:

  • 所有通过addFilterBefore/addFilterAfter添加的过滤器,会在请求到达授权逻辑前依次运行
  • authorizeHttpRequests由过滤器链中的AuthorizationFilter负责处理,属于链条的后期环节

简单说,permitAll()只是授权阶段的放行规则,不会提前跳过前面的过滤器执行。你的JWT过滤器在UsernamePasswordAuthenticationFilter之前,而AuthorizationFilter在更靠后的位置,所以JWT过滤器必然先运行,和后续授权规则无关。

2. addFilterBefore确实会无视授权规则执行

addFilterBefore/addFilterAfter的作用是将过滤器插入到指定位置的过滤器链中,只要请求匹配当前SecurityFilterChain的securityMatcher(这里是/user/**),过滤器就会执行,完全不受authorizeHttpRequests规则影响。

permitAll()不负责跳过过滤器,它仅在授权阶段允许请求通过,不会改变过滤器链的执行流程。

3. 跳过公开端点过滤器的标准模式

模式一:重写shouldNotFilter()方法(当前使用方案)

在自定义OncePerRequestFilter中重写该方法,直接指定需要跳过的路径:

@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
    String path = request.getRequestURI();
    return path.equals("/user/register") || path.equals("/user/login");
}

此方式灵活度高,适合自定义复杂路径规则。

模式二:过滤器内部判断路径直接放行

若不想重写shouldNotFilter(),可在doFilterInternal()开头判断路径,直接放行:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    String path = request.getRequestURI();
    if (path.startsWith("/user/register") || path.startsWith("/user/login")) {
        filterChain.doFilter(request, response);
        return;
    }
    // 原JWT校验逻辑
}

模式三:拆分SecurityFilterChain(推荐设计)

创建两个独立的过滤器链,分别处理公开端点和需认证端点:

// 公开端点过滤器链,优先级更高(Order(1))
@Bean
@Order(1)
public SecurityFilterChain publicUserFilterChain(HttpSecurity http) throws Exception {
    return http.securityMatcher("/user/register", "/user/login")
            .csrf(AbstractHttpConfigurer::disable)
            .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
            // 不添加JWT过滤器
            .build();
}

// 需认证端点过滤器链,优先级较低(Order(2))
@Bean
@Order(2)
public SecurityFilterChain securedUserFilterChain(
        HttpSecurity http, @Qualifier("userAuthManager") AuthenticationManager userAuthManager) throws Exception {
    return http.securityMatcher("/user/me", "/user/update/**", "/user/logout")
            .csrf(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable)
            .cors(Customizer.withDefaults())
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authenticationManager(userAuthManager)
            .addFilterBefore(userJWTFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
}

这种方式符合Spring Security的设计思想,让公开端点完全不经过JWT过滤器,无需额外路径判断。


内容的提问来源于stack exchange,提问作者Hi Byee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 14:30:31