Spring Security配置BcryptEncoder登录提示Bad Credentials问题排查
问题描述
开发Spring Security支撑的REST服务时,已按指引创建配置类定义PasswordEncoder Bean,项目可正常编译,但登录操作始终返回*Bad Credentials(无效凭证)*错误。已确认输入的账号密码完全正确,数据库中存储的Bcrypt格式密码带有{bcrypt}前缀,初步判定为密码编码器配置错误。
现有配置
PasswordEncoderConfig 代码
@Configuration public class PasswordEncoderConfig { @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } }
SpringSecurityConfiguration 代码
@EnableWebSecurity class SecurityConfiguration extends WebSecurityConfigurerAdapter{ private static final String ADMIN = "ROLE_ADMIN"; private static final String WORKER = "ROLE_WORKER"; private final DataSource dataSource; private PasswordEncoder bcryptencoder; public SecurityConfiguration(DataSource dataSource, PasswordEncoder bcryptencoder) { this.dataSource = dataSource; this.bcryptencoder = bcryptencoder; } /*@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }*/ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication().dataSource(dataSource) .usersByUsernameQuery("select voornaam as username, password as password, true as enabled from gebruikers where voornaam = ?") .passwordEncoder(bcryptencoder) .authoritiesByUsernameQuery("select voornaam as username, role as authorities from gebruikers where voornaam = ?"); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring() .mvcMatchers("/images/**") .mvcMatchers("/css/**") .mvcMatchers("/js/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable(); /*remove after postman, @cross origin*/ http.formLogin(); http.authorizeHttpRequests(requests -> requests .mvcMatchers("/**").hasAnyAuthority(ADMIN, WORKER) .mvcMatchers("/gebruikers/**").hasAnyAuthority(ADMIN, WORKER)); http.logout(); } }
故障根因
核心问题出在密码编码器的注入匹配逻辑与密码前缀适配规则不匹配:
PasswordEncoderConfig中定义的DelegatingPasswordEncoder本身支持识别{bcrypt}前缀,会自动提取前缀后调用BCrypt算法完成密码匹配,逻辑本身没有问题,该Bean默认注册名称为passwordEncoder。SecurityConfiguration构造器注入PasswordEncoder时,参数名写为bcryptencoder。如果项目中残留了其他名为bcryptencoder的PasswordEncoderBean(比如注释掉的BCryptPasswordEncoder定义未清理、其他配置类声明了同名Bean),Spring会按参数名优先注入BCryptPasswordEncoder实例。BCryptPasswordEncoder不识别{bcrypt}前缀,会把包含前缀的完整字符串作为BCrypt密文与输入明文匹配,必然匹配失败,抛出Bad Credentials错误。
另外权限配置顺序存在隐患:/**的全路径匹配规则写在/gebruikers/**前面,会导致后续更具体的路径规则永远不会触发,不过该问题不会影响登录阶段的凭证校验。
修复方案
二选一即可:
- 方案1(推荐,兼容多密码算法格式):保留DelegatingPasswordEncoder
- 全局搜索项目中所有
PasswordEncoder类型的Bean定义,删除多余的BCryptPasswordEncoder实例声明,确保上下文里只有PasswordEncoderConfig中定义的DelegatingPasswordEncoder。 - 将
SecurityConfiguration中注入的密码编码器参数名改为passwordEncoder,避免按名称匹配注入错误的Bean,同步修改jdbcAuthentication配置中的编码器引用即可。
- 全局搜索项目中所有
- 方案2:直接使用BCryptPasswordEncoder(无需{bcrypt}前缀)
- 删除
PasswordEncoderConfig类,放开SecurityConfiguration中被注释的BCryptPasswordEncoderBean定义。 - 批量删除数据库中所有存储密码前的
{bcrypt}前缀,BCryptPasswordEncoder不识别该前缀,前缀存在会导致密文格式非法、匹配失败。
- 删除
额外优化
调整HttpSecurity中的权限匹配规则顺序,把范围更小的路径匹配放在前面,避免规则被覆盖:
http.authorizeHttpRequests(requests -> requests .mvcMatchers("/gebruikers/**").hasAnyAuthority(ADMIN, WORKER) .mvcMatchers("/**").hasAnyAuthority(ADMIN, WORKER));
内容的提问来源于stack exchange,提问作者Yannick Mussche
相关产品推荐
相关产品推荐

