密码加密
5.3PasswordEncoder 详解
Spring security中通过PasswordEncoder 接口定义了密码加密和比对的相关操作:
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
5.3.1PasswordEncoder 常见实现类
5.3.2DelegatingPasswordEncoder
在spring security5.0之后,默认的密码加密方案是DelegatingPasswordEncoder 。它是一个代理类,主要用来代理其他不同的密码加密方案。 先从PasswordEncoderFactories 类开始看起,因为正是由它里边的静态方法createDelegatingPasswordEncoder 提供了默认的DelegatingPasswordEncoder 实例:
public final class PasswordEncoderFactories {
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> 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());
encoders.put("argon2", new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
}
接下来分析DelegatingPasswordEncoder :
public class DelegatingPasswordEncoder implements PasswordEncoder {
private static final String PREFIX = "{";
private static final String SUFFIX = "}";
private final String idForEncode;
private final PasswordEncoder passwordEncoderForEncode;
private final Map<String, PasswordEncoder> idToPasswordEncoder;
private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
@Override
public String encode(CharSequence rawPassword) {
return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
if (rawPassword == null && prefixEncodedPassword == null) {
return true;
}
String id = extractId(prefixEncodedPassword);
PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
if (delegate == null) {
return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);
}
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
return delegate.matches(rawPassword, encodedPassword);
}
@Override
public boolean upgradeEncoding(String prefixEncodedPassword) {
String id = extractId(prefixEncodedPassword);
if (!this.idForEncode.equalsIgnoreCase(id)) {
return true;
}
else {
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword);
}
}
}
5.4实战
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("javaboy")
.password("$2a$10$Zg.29wwumBjpQk0xTEBnZeTBWIGxVyth4ByXjdGoRo5.qJZlda9Au")
.roles("admin");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf().disable();
}
}
5.5加密方案自动升级
使用DelegatingPasswordEncoder 的另外一个好处就是会自动进行密码加密方案升级。
@Configuration
public class UserService implements UserDetailsService, UserDetailsPasswordService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails updatePassword(UserDetails user, String newPassword) {
Integer result = userMapper.updatePassword(user.getUsername, newPassword);
if (result == 1) {
((User) user).setPassword(newPassword);
}
return user;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userMapper.loadUserByUsername(username);
}
}
例如DaoAuthenticationProvider 中的createSuccessAuthentication 方法中就触发了加密方案的自动升级。
5.6是谁的PasswordEncoder
如果开发者向spring容器中注册了一个PasswordEncoder 实例,那么无论是全局的AuthenticationManager 还是局部的,都将使用该实例;如果开发者没有提供任何PasswordEncoder 实例,那么无论是全局的还是局部的,都将使用DelegatingPasswordEncoder 实例。
|