spring-boot 1.5.3 升级到 2.1.7 出现上述错误,查看MAVEN引用信息,引用的spring security版本为5.1.16,其官方文档地址为:https://docs.spring.io/spring...
原理猜想
报错的代码在这:
package org.springframework.security.crypto.password;
public class DelegatingPasswordEncoder implements PasswordEncoder {
@Override
public boolean matches(CharSequence rawPassword,
String prefixEncodedPassword) {
String id = extractId(prefixEncodedPassword);
throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");
}
}
根据异常排查,大概的思想是这样:
- 获取密码
- 获取默认加密、密码匹配对象
1.1 获取获取的加密类型(加密前缀)
1.2 根据类型找算法
1.3 没找到算法,则调用默认算法
1.4 默认算法代码如上,抛出异常:
第1.1步我给几个例子,帮助学习:
- 由密码
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
获取到的加密类型为bcrypt
. - 由密码
{noop}password
获取到加密类开地为noop
- 由密码
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
中获取到的加密类型为sha256
。
具体的出错的逻辑是这样的:
- 获取到了密码,比如为
123456
. - 获取默认加密、密码匹配对象:
DelegatingPasswordEncoder
1.1 spring尝试从123456
中,获取一个加密前缀
的东西。但获取的值为null。
1.2 没有找到算法,则调用默认算法,此时默认对象为:UnmappedIdPasswordEncoder
1.3 运行对象UnmappedIdPasswordEncoder
的matches
算法
1.4 抛出异常。
解决问题
spring security支持的列表如下:
String encodingId = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
使用了预制算法
可以将数据库中密码更新为{算法前缀}原密码
,来进行升级。新增数据时,密码字段也要加入前缀。
原来未使用加密算法
可以将数据库中密码更新为{noop}原密码
,来进行升级。新增数据时,密码字段也要加入前缀{noop}
。
自定义的算法
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.authenticationProvider(authProvider());
}
private DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
private PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
return (String) rawPassword;
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.equals(encodedPassword);
}
};
}
}
注意:直接使用官方文档推荐的方法并不生效,猜想原因应该是升级前版本不匹配。官方文档说 If you are migrating from Spring Security 4.2.x you can revert to the previous behavior by exposing a NoOpPasswordEncoder bean.
,我的只所以没有生效,应该是前版本不是4.2.x,在此未做验证。
注意:在升级前的定义方法已失效,比如:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsService);
.passwordEncoder(passwordEncoder());
}
private PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
// 自定义加密算法
return (String) rawPassword;
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
// 自定义匹配算法
return rawPassword.equals(encodedPassword);
}
};
}
}
错误信息如下
Error:(49, 17) java: 无法访问org.springframework.security.authentication.encoding.PasswordEncoder
找不到org.springframework.security.authentication.encoding.PasswordEncoder的类文件
新项目
新项目,在进行密码匹配时,会根据前缀自动调用密码匹配算法。所以,我们只需要在保存用户时,为其调用合适的算法,并设置相应的前缀即可。在此,建立直接调用官方的:
String 加密后的密码 = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("原密码");