Spring Security报错:无映射id为null的PasswordEncoder,求解决方案
问题描述
报错信息:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
相关实现代码如下:
SecurityConfig配置类
@RequiredArgsConstructor @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { private final UserDetailsService userDetailsService; private final PersistentTokenRepository persistentTokenRepository; // 用于Spring Data JPA SPeL @Bean public SecurityEvaluationContextExtension securityEvaluationContextExtension() { return new SecurityEvaluationContextExtension(); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(authorize -> { authorize .antMatchers("/", "/webjars/**", "/login", "/resources/**").permitAll(); } ) .authorizeRequests() .anyRequest().authenticated() .and() .formLogin(loginConfigurer -> { loginConfigurer .loginProcessingUrl("/login") .loginPage("/").permitAll() .successForwardUrl("/") .defaultSuccessUrl("/") .failureUrl("/?error"); }) .logout(logoutConfigurer -> { logoutConfigurer .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")) .logoutSuccessUrl("/?logout") .permitAll(); }) .httpBasic() .and().rememberMe() .tokenRepository(persistentTokenRepository) .userDetailsService(userDetailsService); http.headers().frameOptions().sameOrigin(); } @Bean PasswordEncoder passwordEncoder(){ return SfgPasswordEncoderFactories.createDelegatingPasswordEncoder(); } }
Token创建配置
@Configuration public class SecurityBeans { @Bean public PersistentTokenRepository persistentTokenRepository(DataSource dataSource){ JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(dataSource); return tokenRepository; } }
自定义PasswordEncoder工厂类
public class SfgPasswordEncoderFactories { public static PasswordEncoder createDelegatingPasswordEncoder() { String encodingId = "bcrypt"; Map<String, PasswordEncoder> encoders = new HashMap<>(); encoders.put(encodingId, new BCryptPasswordEncoder()); encoders.put("bcrypt", new BCryptPasswordEncoder()); encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder()); encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()); encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder()); return new DelegatingPasswordEncoder(encodingId, encoders); } // 禁止实例化 private SfgPasswordEncoderFactories() { } }
用户说明:数据库中已插入bcrypt格式的密码,错误出在return new DelegatingPasswordEncoder(encodingId, encoders);行,但认为代码正确。
问题原因
DelegatingPasswordEncoder默认要求密码字符串以{编码ID}作为前缀(比如{bcrypt}$2a$10$...),用来识别使用哪种编码器校验密码。如果数据库中的密码没有这个前缀,DelegatingPasswordEncoder会无法识别编码ID,导致抛出"id 'null'"的错误。
虽然你在工厂类中设置了默认编码ID为bcrypt,但DelegatingPasswordEncoder的默认逻辑是必须通过前缀指定编码ID,不会自动使用默认编码器处理无前缀的密码。
解决方案
方案1:修改数据库密码,添加前缀
将数据库中存储的bcrypt密码前面加上{bcrypt}前缀,例如:
原密码:$2a$10$EixZaY3s7vjR0kS6Rq7M/.tG8fL93K5y8eQbX8y8eQbX8y8eQbX8y
修改后:{bcrypt}$2a$10$EixZaY3s7vjR0kS6Rq7M/.tG8fL93K5y8eQbX8y8eQbX8y8eQbX8y
方案2:配置DelegatingPasswordEncoder使用默认编码器处理无前缀密码
修改SfgPasswordEncoderFactories的createDelegatingPasswordEncoder方法,创建DelegatingPasswordEncoder后,设置默认编码器(当无前缀时使用bcrypt):
public static PasswordEncoder createDelegatingPasswordEncoder() { String encodingId = "bcrypt"; Map<String, PasswordEncoder> encoders = new HashMap<>(); encoders.put(encodingId, new BCryptPasswordEncoder()); encoders.put("bcrypt", new BCryptPasswordEncoder()); encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder()); encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()); encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder()); DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(encodingId, encoders); // 设置默认编码器,处理无前缀的密码 delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder()); return delegatingPasswordEncoder; }
方案3:直接使用BCryptPasswordEncoder(如果仅需bcrypt编码)
如果你的系统只需要bcrypt一种密码编码方式,可以简化PasswordEncoder的Bean定义,直接返回BCryptPasswordEncoder,无需使用DelegatingPasswordEncoder:
@Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }
这样数据库中的密码无需添加前缀,直接存储bcrypt加密后的字符串即可。
验证建议
修改后重新启动应用,测试登录功能,确认错误消失。同时确保UserDetailsService实现中返回的User对象的密码与数据库存储的格式一致。
内容的提问来源于stack exchange,提问作者Javier Figueiras

