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

Spring Boot3+Security6无法放行指定URL的问题排查

Spring Boot 3 + Spring Security 6:放行/health等URL仍返回401的解决方案

我在开发Spring Boot 3项目时,集成Spring Security 6实现基于请求头Authorization Token的授权逻辑,希望让/health、/signup等URL无需Token即可访问,但目前访问这些路径时返回401,携带Token则正常返回200。

我的SecurityConfig和JwtFilter代码如下:

现有代码

SecurityConfiguration

@Configuration
@EnableWebSecurity
@Order(1)
class SecurityConfiguration(private val jwtFilter : JwtFilter) {
    @Bean
    fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http.csrf().disable().
        authorizeHttpRequests { authorize ->
            authorize
                .requestMatchers("/health").permitAll()
                .anyRequest().authenticated()
        }

        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter::class.java)
        return http.build()
    }
}

JwtFilter

@Component
class JwtFilter : Filter {
    override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
        val httpRequest = request as HttpServletRequest
        val httpResponse = response as HttpServletResponse
        val jwt = httpRequest.getHeader("Authorization")?.removePrefix("Bearer ")
        if(jwt != null){
            val extractedToken = verifyJwtWithLambda(jwt)
            val isValid : Boolean? = extractedToken["isValid"] as? Boolean
            if(isValid!!){
                val attributes = extractedToken["attributes"] as? Map<*, *>
                val subject = attributes?.get("userId") as? String
                val authentication = UsernamePasswordAuthenticationToken(subject, null, emptyList())
                SecurityContextHolder.getContext().authentication = authentication
                chain.doFilter(request, response)
            }
            else {
                httpResponse.status = HttpServletResponse.SC_UNAUTHORIZED
            }
        }
        else {
                httpResponse.status = HttpServletResponse.SC_UNAUTHORIZED
        }
    }
}

我尝试过添加WebSecurityCustomizer忽略URL、用.anonymous替代.permitAll等方案,都无效;在JwtFilter里加路径判断虽能解决,但不够优雅。


问题根源

你的JwtFilter直接实现了Servlet标准的Filter接口,它由Servlet容器管理,执行优先级高于Spring Security的过滤器链。也就是说,Spring Security配置的permitAll还没生效,自定义过滤器就已经拦截了请求,发现没有Authorization头就直接返回401了。

解决方案:改用Spring Security的OncePerRequestFilter

Spring Security提供了OncePerRequestFilter,专门用于在每个请求中执行一次过滤逻辑,并且可以通过shouldNotFilter方法指定不需要过滤的路径,完美适配你的需求。

1. 修改JwtFilter继承OncePerRequestFilter

@Component
class JwtFilter : OncePerRequestFilter() {
    // 指定不需要过滤的路径
    override fun shouldNotFilter(request: HttpServletRequest): Boolean {
        val path = request.servletPath
        return path == "/health" || path == "/signup"
    }

    override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain
    ) {
        val jwt = request.getHeader("Authorization")?.removePrefix("Bearer ")
        if(jwt != null){
            val extractedToken = verifyJwtWithLambda(jwt)
            val isValid : Boolean? = extractedToken["isValid"] as? Boolean
            if(isValid!!){
                val attributes = extractedToken["attributes"] as? Map<*, *>
                val subject = attributes?.get("userId") as? String
                val authentication = UsernamePasswordAuthenticationToken(subject, null, emptyList())
                SecurityContextHolder.getContext().authentication = authentication
                filterChain.doFilter(request, response)
            }
            else {
                response.status = HttpServletResponse.SC_UNAUTHORIZED
            }
        }
        else {
            response.status = HttpServletResponse.SC_UNAUTHORIZED
        }
    }
}

2. 调整SecurityConfig(优化配置)

移除不必要的@Order(1)(除非你有多个过滤器链需要排序),确保配置清晰:

@Configuration
@EnableWebSecurity
class SecurityConfiguration(private val jwtFilter : JwtFilter) {
    @Bean
    fun filterChain(http: HttpSecurity): SecurityFilterChain {
        http.csrf().disable()
            .authorizeHttpRequests { authorize ->
                authorize
                    .requestMatchers("/health", "/signup").permitAll()
                    .anyRequest().authenticated()
            }
            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter::class.java)
        return http.build()
    }
}

为什么之前的尝试无效?

  • WebSecurityCustomizer忽略URL无效:WebSecurity的ignoring()只会让路径跳过Spring Security过滤器链,但你的JwtFilter是Servlet容器管理的,不受此配置影响。
  • .anonymous替代.permitAll无效:自定义过滤器先于Spring Security授权环节执行,还没到判断是否允许匿名访问的步骤就返回了401。
  • addFilterBefore/After调整无效:无论调整在UsernamePasswordAuthenticationFilter的前后,自定义过滤器都会拦截所有请求,包括配置了permitAll的路径。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 02:07:07