Spring Boot 2.6.4多Security配置JWT与Actuator认证冲突问题
问题根因
你踩了Spring Security多WebSecurityConfigurerAdapter配置的三个典型坑:
- 没给每个配置类明确划定管辖的请求范围:很多人会把
authorizeRequests()里的路径规则当成配置类的生效范围,其实这部分只控制当前过滤器链内的权限判断逻辑,真正决定当前Security过滤器链处理哪些请求的是requestMatchers()配置。不写的话每个配置类默认匹配所有请求,Spring会按@Order从小到大选第一个匹配的链处理请求,后续链直接跳过,根本不会执行。 - 角色/权限前缀不匹配:
hasRole("MODERATOR")会自动给权限值拼接ROLE_前缀再做匹配,但你给内存用户配置的是authorities("MODERATOR"),没有对应前缀,就算认证成功也会报403。 - AuthenticationEntryPoint注入冲突:你在Actuator配置里直接
@Autowired AuthenticationEntryPoint,容器里优先注入了你自定义的JwtAuthenticationEntryPoint,导致Basic认证失败时走了JWT的错误返回逻辑,不会触发Basic认证的弹窗流程。
之前两种Order配置的异常原因也很明确:
- 给Actuator配置设
@Order(-1)时,它优先级最高,接管了所有请求:业务路径没在这个配置里加认证规则,直接被默认放行导致裸奔;Actuator路径走Basic认证时因为入口点被注入成JWT的实现,直接报错提示需要完整认证。 - 给Actuator配置设
@Order(2)时,优先级为1的Api配置先接管了所有请求(包括/actuator/**),直接走JWT认证逻辑,Actuator的Basic配置根本不会触发。
修复方案
按以下规则调整配置即可:
- 给Actuator配置设最高优先级(比如
@Order(1)),通过requestMatchers()明确指定它只管辖/actuator/**路径的请求,修正权限匹配逻辑,显式定义Basic认证的入口点避免和JWT冲突,关闭csrf避免Actuator的写端点被拦截。 - 给Api配置设次一级优先级(比如
@Order(2)),因为Actuator路径已经被高优先级链接管,当前链默认处理剩余所有业务请求即可,保留原有JWT认证逻辑。 - 修正内存用户的权限配置,用
roles("MODERATOR")自动适配hasRole的前缀规则,避免权限匹配失败。 - 两个内部配置类加
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
相关产品推荐
相关产品推荐

