Spring Security OAuth2与Basic认证配置问题:Prometheus访问Actuator报BadCredential异常排查
这个问题我之前也碰到过!核心原因是你的服务作为OAuth2资源服务器,默认的Spring Security过滤器链是用来处理OAuth2令牌认证的,而你给Prometheus配置的是HTTP Basic认证——这俩默认不兼容,所以当你给/actuator/**加上角色权限控制时,Prometheus的Basic Auth凭证根本不被识别,自然就抛出BadCredential异常了。
下面给你两种可行的解决方案,我推荐第一种,逻辑更清晰:
方案一:为Actuator端点单独配置Basic Auth安全链
因为Actuator的监控端点不需要走业务的OAuth2认证,我们可以把它从ResourceServer的拦截范围中排除,单独给它配置一套Basic Auth的安全规则。
步骤1:修改ResourceServerConfiguration,排除Actuator端点
修改你的ResourceServerConfiguration,让/actuator/**的请求不经过OAuth2资源服务器的认证,交给专门的配置处理:
@Configuration @EnableResourceServer @EnableGlobalMethodSecurity(prePostEnabled = true) public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() // 把Actuator端点交给专门的安全配置处理 .antMatchers("/actuator/**").permitAll() .antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**", "/swagger-resources/configuration/ui", "/swagger-resources/configuration/security").permitAll() .antMatchers("/**").authenticated(); } }
步骤2:创建Actuator专属的安全配置类
新建一个配置类,专门处理Actuator的Basic Auth认证,注意要设置@Order(1)让它的优先级高于ResourceServer(ResourceServer默认Order是3):
@Configuration @Order(1) public class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter { @Value("${spring.security.name}") private String actuatorUsername; @Value("${spring.security.password}") private String actuatorPassword; @Value("${spring.security.roles}") private String actuatorRole; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() // 只拦截Actuator相关请求 .antMatcher("/actuator/**") .authorizeRequests() // 要求必须拥有指定角色 .anyRequest().hasRole(actuatorRole) // 启用HTTP Basic认证 .and() .httpBasic(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 配置内存用户认证(生产环境建议用数据库存储,配合密码编码器) auth.inMemoryAuthentication() .withUser(actuatorUsername) // 注意:Spring Security 5+要求必须指定密码编码器,{noop}表示明文(生产环境禁用!) .password("{noop}" + actuatorPassword) .roles(actuatorRole); } // 生产环境请使用这个密码编码器,替换上面的{noop} // @Bean // public PasswordEncoder passwordEncoder() { // return new BCryptPasswordEncoder(); // } }
注意事项
- 生产环境绝对不能用
{noop}明文密码!要启用上面注释的BCryptPasswordEncoder,然后把application.yml里的密码换成BCrypt加密后的字符串。 @Order(1)是关键:确保Actuator的安全配置先于ResourceServer生效,这样/actuator/**的请求会优先走这套规则。
方案二:在ResourceServer中同时支持OAuth2和Basic Auth(可选)
如果你不想拆分配置,也可以在ResourceServer中添加Basic Auth的认证提供者,让系统同时支持OAuth2令牌和Basic Auth。不过这种方式逻辑稍复杂,不如第一种直观:
@Configuration @EnableResourceServer @EnableGlobalMethodSecurity(prePostEnabled = true) public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Value("${spring.security.name}") private String actuatorUsername; @Value("${spring.security.password}") private String actuatorPassword; @Value("${spring.security.roles}") private String actuatorRole; @Override public void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**", "/swagger-resources/configuration/ui", "/swagger-resources/configuration/security").permitAll() .antMatchers("/actuator/**").hasRole(actuatorRole) .antMatchers("/**").authenticated() .and() // 添加Basic Auth过滤器 .addFilterBefore(basicAuthFilter(), UsernamePasswordAuthenticationFilter.class); } private BasicAuthenticationFilter basicAuthFilter() throws Exception { return new BasicAuthenticationFilter(authenticationManager()); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 先保留原有的OAuth2认证逻辑(如果有的话) super.configure(auth); // 添加Actuator的Basic Auth用户 auth.inMemoryAuthentication() .withUser(actuatorUsername) .password("{noop}" + actuatorPassword) .roles(actuatorRole); } }
这种方式需要确保Basic Auth过滤器被添加到正确的位置,同时处理好和OAuth2过滤器的优先级,所以更推荐第一种方案。
最后检查你的Prometheus配置,目前的basic_auth配置是正确的,只要用户名密码和我们配置的一致,就能正常拉取指标了。
内容的提问来源于stack exchange,提问作者Khilarian

