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

Spring Boot 2.6.4多Security配置JWT与Actuator认证冲突问题

问题根因

你踩了Spring Security多WebSecurityConfigurerAdapter配置的三个典型坑:

  1. 没给每个配置类明确划定管辖的请求范围:很多人会把authorizeRequests()里的路径规则当成配置类的生效范围,其实这部分只控制当前过滤器链内的权限判断逻辑,真正决定当前Security过滤器链处理哪些请求的是requestMatchers()配置。不写的话每个配置类默认匹配所有请求,Spring会按@Order从小到大选第一个匹配的链处理请求,后续链直接跳过,根本不会执行。
  2. 角色/权限前缀不匹配:hasRole("MODERATOR")会自动给权限值拼接ROLE_前缀再做匹配,但你给内存用户配置的是authorities("MODERATOR"),没有对应前缀,就算认证成功也会报403。
  3. AuthenticationEntryPoint注入冲突:你在Actuator配置里直接@Autowired AuthenticationEntryPoint,容器里优先注入了你自定义的JwtAuthenticationEntryPoint,导致Basic认证失败时走了JWT的错误返回逻辑,不会触发Basic认证的弹窗流程。

之前两种Order配置的异常原因也很明确:

  • 给Actuator配置设@Order(-1)时,它优先级最高,接管了所有请求:业务路径没在这个配置里加认证规则,直接被默认放行导致裸奔;Actuator路径走Basic认证时因为入口点被注入成JWT的实现,直接报错提示需要完整认证。
  • 给Actuator配置设@Order(2)时,优先级为1的Api配置先接管了所有请求(包括/actuator/**),直接走JWT认证逻辑,Actuator的Basic配置根本不会触发。

修复方案

按以下规则调整配置即可:

  1. 给Actuator配置设最高优先级(比如@Order(1)),通过requestMatchers()明确指定它只管辖/actuator/**路径的请求,修正权限匹配逻辑,显式定义Basic认证的入口点避免和JWT冲突,关闭csrf避免Actuator的写端点被拦截。
  2. 给Api配置设次一级优先级(比如@Order(2)),因为Actuator路径已经被高优先级链接管,当前链默认处理剩余所有业务请求即可,保留原有JWT认证逻辑。
  3. 修正内存用户的权限配置,用roles("MODERATOR")自动适配hasRole的前缀规则,避免权限匹配失败。
  4. 两个内部配置类加static修饰,避免被Spring上下文扫描优先级影响导致配置不生效。

修正后的完整配置代码

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        securedEnabled = true,
        jsr250Enabled = true,
        prePostEnabled = true)
public class SecurityConfig {
    
    @Order(1) // Actuator配置优先级最高
    @Configuration
    public static class ActuatorSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        private final BasicAuthenticationEntryPoint basicAuthenticationEntryPoint;

        public ActuatorSecurityConfigurerAdapter() {
            // 手动初始化Basic认证入口点,避免和JWT入口点注入冲突
            this.basicAuthenticationEntryPoint = new BasicAuthenticationEntryPoint();
            this.basicAuthenticationEntryPoint.setRealmName("Actuator Realm");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    // 核心配置:明确当前过滤器链只处理actuator路径
                    .requestMatchers().antMatchers("/actuator/**")
                    .and()
                    .cors().and().csrf().disable()
                    .exceptionHandling().authenticationEntryPoint(basicAuthenticationEntryPoint).and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    .authorizeRequests()
                    .antMatchers("/actuator/**").hasRole("MODERATOR")
                    .and()
                    .httpBasic().authenticationEntryPoint(basicAuthenticationEntryPoint);
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("user1")
                    .password(passwordEncoder().encode("user1"))
                    // roles方法会自动添加ROLE_前缀,适配hasRole的匹配规则
                    .roles("MODERATOR");
        }
    }

    @Order(2) // Api配置优先级次一级
    @Configuration
    public static class ApiSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        private final CustomUserDetailsServiceImpl customUserDetailsService;
        private final JwtAuthenticationEntryPoint unauthorizedHandler;
        private final JwtAuthenticationFilter jwtAuthenticationFilter;

        public ApiSecurityConfigurationAdapter(CustomUserDetailsServiceImpl customUserDetailsService, JwtAuthenticationEntryPoint unauthorizedHandler, JwtAuthenticationFilter jwtAuthenticationFilter) {
            this.customUserDetailsService = customUserDetailsService;
            this.unauthorizedHandler = unauthorizedHandler;
            this.jwtAuthenticationFilter = jwtAuthenticationFilter;
        }

        @Bean(BeanIds.AUTHENTICATION_MANAGER)
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .cors().and().csrf().disable()
                    .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    .authorizeRequests()
                    .antMatchers(Endpoints.AUTH_ENDPOINT+"/**").permitAll()
                    .anyRequest()
                    .authenticated();

            http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(customUserDetailsService)
                    .passwordEncoder(passwordEncoder());
        }

    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 01:27:26