Spring Security下permitAll()为何触发AuthenticationManagerResolver
这不是Spring Security的bug,本质是对permitAll()的语义理解存在偏差,结合过滤器的固定执行顺序,就会出现你观测到的现象。
Spring Security的过滤器链按优先级顺序执行:你配置oauth2ResourceServer()后注册的BearerTokenAuthenticationFilter,执行顺序远早于处理authorizeRequests路径匹配规则的AuthorizationFilter。
BearerTokenAuthenticationFilter的逻辑是:只要检测到请求携带Bearer令牌,不管后续路径配置的权限规则是什么,都会先调用你配置的AuthenticationManagerResolver获取认证管理器,完成令牌校验、填充SecurityContext的操作;如果令牌校验失败,会直接抛出认证异常,触发authenticationEntryPoint返回401,根本不会走到后续的路径权限匹配步骤。permitAll()本质是授权规则,不是「跳过认证流程」的配置,它的实际语义是:当请求走到授权判断环节时,无论当前请求的认证状态是匿名、已认证还是其他状态,都直接放行。它无权干预前置过滤器的认证逻辑。
你观测到的「访问permitAll端点时AuthenticationManagerResolver被触发」完全是默认逻辑下的正常表现:
- 请求没带令牌:BearerTokenAuthenticationFilter直接放行,走到授权环节匹配permitAll规则,正常访问
- 请求带有效令牌:过滤器正常完成认证,走到授权环节匹配permitAll规则,正常访问
- 请求带无效令牌:过滤器在前置校验阶段就抛出异常,直接返回401,走不到permitAll的判断逻辑,这就是你觉得不符合预期的场景
根据业务场景选其中一种即可:
方案1:自定义令牌解析器,对公开路径跳过令牌校验
这种方式不会把路径排除在安全过滤器链外,cors、csrf等安全配置仍然生效,是更推荐的做法。
你可以自定义BearerTokenResolver,对配置了permitAll的路径直接返回null,告诉BearerTokenAuthenticationFilter当前请求不需要解析令牌,就不会触发AuthenticationManagerResolver的调用:
@Bean public BearerTokenResolver bearerTokenResolver() { DefaultBearerTokenResolver defaultResolver = new DefaultBearerTokenResolver(); return request -> { String servletPath = request.getServletPath(); // 匹配所有配置permitAll的路径 if (servletPath.startsWith("/endpoint/") || servletPath.equals("/error") || servletPath.equals("/info") || servletPath.equals("/health")) { return null; } return defaultResolver.resolve(request); }; }
然后在你的HttpSecurity配置中注入这个解析器:
http .cors().configurationSource(corsConfigurationSource) .and() .csrf() .ignoringAntMatchers("/") .and() .authorizeRequests(authorizeRequests -> authorizeRequests .mvcMatchers(GET, "/endpoint").hasAuthority("SCOPE_" + (Scope.READ)) .mvcMatchers(GET, "/endpoint/{reference}").permitAll() .mvcMatchers(GET, "/error").permitAll() .mvcMatchers(GET, "/info").permitAll() .mvcMatchers(GET, "/health").permitAll() .anyRequest().denyAll()) .oauth2ResourceServer() // 注入自定义的令牌解析器 .bearerTokenResolver(bearerTokenResolver()) .authenticationManagerResolver(authenticationManager()) .accessDeniedHandler(accessDeniedHandler) .authenticationEntryPoint(authenticationEntryPoint);
方案2:将公开路径直接排除出安全过滤器链
如果你的公开端点完全不需要任何安全逻辑(比如纯静态资源、健康检查端点),可以通过WebSecurityCustomizer直接把这些路径从整个安全过滤器链中忽略,不会走任何安全相关的过滤器:
@Bean public WebSecurityCustomizer webSecurityCustomizer() { return web -> web.ignoring().mvcMatchers( GET, "/endpoint/{reference}", "/error", "/info", "/health" ); }
注意:这种方式下,这些路径的cors、csrf、安全响应头等配置都会失效,如果需要这些能力要单独在全局配置里处理,灵活性不如方案1。
内容的提问来源于stack exchange,提问作者Musik Forum

