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

Spring Security 无法同时支持需认证与免认证端点的问题求助

问题根因

  1. 安全配置写法错误:configure(HttpSecurity http)方法中重复调用了两次authorizeRequests(),且自定义的JWT权限转换器没有加@Bean注解,也没有注册到OAuth2资源服务器配置中,导致JWT校验逻辑异常,公开路径的permitAll规则被后续的全局认证规则覆盖,访问公开接口返回401。
  2. 认证拦截逻辑失效:调整配置时错误放开了全局匿名访问权限,导致原本需要认证的端点没有被Spring Security拦截,未携带JWT的请求直接进入Controller层,Authentication对象为空触发空指针,抛出500错误。
  3. 规则匹配顺序错误:Spring Security的路径匹配规则遵循从上到下优先匹配原则,错误的配置顺序导致公开路径规则未生效。

解决方案

第一步:修正SecurityConfig配置

核心修改点:

  • 合并两次authorizeRequests()配置,将公开路径规则放在最前面
  • 给jwtAuthenticationConverter()方法添加@Bean注解,并注册到JWT配置中
  • 保留全局认证规则,确保非公开路径必须经过认证才能访问
@Configuration
@EnableWebSecurity(debug = false)
@EnableGlobalMethodSecurity(
        securedEnabled = true,
        jsr250Enabled = true,
        prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${auth0.audience}")
    private String audience;

    @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private String issuer;

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:3010"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
        configuration.setAllowCredentials(true);
        configuration.addAllowedHeader("Authorization");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .headers().referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN)
                .and()
                .xssProtection()
                .and()
                .contentSecurityPolicy("script-src 'self'").and()
                .and()
                .csrf()
                .disable()
                .formLogin()
                .disable()
                .httpBasic()
                .disable()
                .exceptionHandling()
                .authenticationEntryPoint(new RestAuthenticationEntryPoint())
                .and()
                // 路径匹配规则按顺序生效,公开路径放前面
                .authorizeRequests()
                .antMatchers("/api/v1/admin/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt()
                // 注册自定义的JWT权限转换器
                .jwtAuthenticationConverter(jwtAuthenticationConverter());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/health", "/health/**");
    }

    @Bean
    JwtDecoder jwtDecoder() {
        NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
                JwtDecoders.fromOidcIssuerLocation(issuer);

        OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
        OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
        OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);

        jwtDecoder.setJwtValidator(withAudience);

        return jwtDecoder;
    }

    // 新增@Bean注解,让Spring托管该转换器
    @Bean
    JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
        converter.setAuthoritiesClaimName("permissions");
        converter.setAuthorityPrefix("");

        JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
        jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
        return jwtConverter;
    }
}

第二步:验证效果

  • 公开路径/api/v1/admin/**无需携带JWT即可正常访问
  • 其他需要认证的路径未携带JWT时,会直接被Spring Security拦截,触发你实现的RestAuthenticationEntryPoint返回401错误,不会进入Controller层,不会出现空指针500问题
  • 携带合法JWT访问认证路径时,Authentication对象会正常注入,原有业务逻辑无需修改

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 19:39:03