PassworderEncoder 详解
1320字约4分钟
2024-08-08
主要方法
String encode(CharSequence rawPassword)
:原始密码加密boolean matches(CharSequence rawPassword, String encodedPassword)
:密码匹配boolean upgradeEncoding(String encodedPassword)
:再次编码已编码密码
PasswordEncoder 接口源码
package org.springframework.security.crypto.password;
/**
* Service interface for encoding passwords.
*
* The preferred implementation is {@code BCryptPasswordEncoder}.
*
* @author Keith Donald
*/
public interface PasswordEncoder {
/**
* Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
* greater hash combined with an 8-byte or greater randomly generated salt.
* 对原始密码进行加密。一个好的编码算法应该将 SHA-1 或者更大的散列与 8字节或更大的随机盐结合使用
*/
String encode(CharSequence rawPassword);
/**
* Verify the encoded password obtained from storage matches the submitted raw
* password after it too is encoded. Returns true if the passwords match, false if
* they do not. The stored password itself is never decoded.
* 验证存储的编码密码与提交的原始密码在编码后是否匹配。匹配返回 true,不匹配则返回 false,
* 存储的密码本身永远不会被解码
* @param rawPassword the raw password to encode and match
* @param encodedPassword the encoded password from storage to compare with
* @return true if the raw password, after encoding, matches the encoded password from
* storage
*/
boolean matches(CharSequence rawPassword, String encodedPassword);
/**
* Returns true if the encoded password should be encoded again for better security,
* else false. The default implementation always returns false.
* 如果为提高安全性需要再次编码已编码的密码,则返回true,否则返回false。默认实现总是返回false。
* @param encodedPassword the encoded password to check
* @return true if the encoded password should be encoded again for better security,
* else false.
*/
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
主要实现类
DelegatingPasswordEncoder:加密代理 默认的PassworderEncoder实例
BCryptPasswordEncoder:DelegatingPasswordEncoder 中默认的加密方式
NoOpPasswordEncoder:不加密
LazyPasswordEncoder:需要用的时候才初始化
MessageDigestPasswordEncoder
Md4PasswordEncoder
默认的 PassworderEncoder 实例
在 PasswordEncoder 创建的工厂类中,定义了很多加密类型,调用 DelegatingPasswordEncoder 代理类初始化中,传递的加密类型 id 是 bcrypt,所以默认的就是 BCryptPasswordEncoder 这个加密类
public final class PasswordEncoderFactories {
@SuppressWarnings("deprecation")
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());
// encodingId 值为 bcrypt,看后下面 DelegatingPasswordEncoder 初始化代码,可以知道 BCryptPasswordEncoder 是 DelegatingPasswordEncoder 中默认的加密方式
return new DelegatingPasswordEncoder(encodingId, encoders);
}
}
public class DelegatingPasswordEncoder implements PasswordEncoder {
private static final String DEFAULT_ID_PREFIX = "{";
private static final String DEFAULT_ID_SUFFIX = "}";
private final String idPrefix;
private final String idSuffix;
private final String idForEncode;
private final PasswordEncoder passwordEncoderForEncode;
private final Map<String, PasswordEncoder> idToPasswordEncoder;
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
this(idForEncode, idToPasswordEncoder, DEFAULT_ID_PREFIX, DEFAULT_ID_SUFFIX);
}
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder,
String idPrefix, String idSuffix) {
// 省略代码......
// idForEncode 是 PasswordEncoderFactories.createDelegatingPasswordEncoder() 传递的 bcrypt
this.idForEncode = idForEncode;
// idToPasswordEncoder 这里个是加密类(Map 类型),key 值 bcrypt 获取的就是 BCryptPasswordEncoder 这个加密类
this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder);
this.idPrefix = idPrefix;
this.idSuffix = idSuffix;
}
}
密码验证逻辑
public class DelegatingPasswordEncoder implements PasswordEncoder {
@Override
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
if (rawPassword == null && prefixEncodedPassword == null) {
return true;
}
// 从 prefixEncodedPassword 中获取加密类型 id, prefixEncodedPassword 数据格式类似:{bcrypt}ac487cc5d0df83aa3f4d130b1
String id = extractId(prefixEncodedPassword);
// 根据加密类型 id 获取加密类,id 为 bcrypt 的话,获取到的就是 BCryptPasswordEncoder
PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
if (delegate == null) {
return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);
}
// 提取存储已编码的密码。即 {bcrypt}ac487cc5d0df83aa3f4d130b1 提取出 ac487cc5d0df83aa3f4d130b1
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
// 匹配原始密码与存储的密码
return delegate.matches(rawPassword, encodedPassword);
}
}
默认的 provider 提供的密码处理器
如果IOC容器中有且仅有一个 PasswordEncoder,那么就使用 IOC 中的 PassworderEncoder;如果没有,则使用 DaoAuthenticationProvider 自己提供的,看构造方法逻辑
class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class);
if (userDetailsService == null) {
return;
}
// IOC 容器中有且仅有一个 PasswordEncoder 的时候才能获取到
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
// 点进去可以看一看,DaoAuthenticationProvider 构造方法逻辑
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
// 获取到的 PasswordEncoder 为空,那么就使用 DaoAuthenticationProvider 中默认密码处理器
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
}
/**
* @return a bean of the requested class if there's just a single registered
* component, null otherwise.
*/
private <T> T getBeanOrNull(Class<T> type) {
String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
if (beanNames.length != 1) {
return null;
}
return InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
}
}
DaoAuthenticationProvider
构造方法逻辑,看到上面刚说的 默认的 PassworderEncoder 实例
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
public DaoAuthenticationProvider() {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
}
Passworder 实战
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
</dependencies>
IndexController 登录成功返回当前用户信息
@Controller
public class IndexController {
/**
* 从 SecurityContextHolder 获取信息
* @return
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
@ResponseBody
public String index() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return JSON.toJSONString(authentication);
}
}
密码加密
public static void main(String[] args) {
System.out.println("{pbkdf2}"+new Pbkdf2PasswordEncoder().encode("1234"));
System.out.println("{bcrypt}"+new BCryptPasswordEncoder().encode("1234"));
}
基于内存方式配置用户数据
/**
* @ClassName SecurityConfig
* @Desciption
* @Author MaRui
* @Date 2023/9/21 16:23
* @Version 1.0
*/
@Configuration
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(){
UserDetails noop = User.withUsername("noop").password("{noop}1234").roles("admin").build();
UserDetails bcypt = User.withUsername("bcrypt").password("{bcrypt}$2a$10$MI6ueeZD8uhAbCy1SH2FSuTxkARMc2x6Lzw.x4ax0ybpoXJLIrl8u").roles("admin").build();
UserDetails pbkdf2 = User.withUsername("pbkdf2").password("{pbkdf2}ac487cc5d0df83aa3f4d130b1c94063feb6facfc597266175384b78eb432c382fe3aef332ffaff34").roles("admin").build();
return new InMemoryUserDetailsManager(bcypt);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((auth) ->{
try {
auth.anyRequest().authenticated()
.and().formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and()
.csrf().disable();
}
catch (Exception e){
}
});
return http.build();
}
}
验证一下,访问登录页面 http://127.0.0.1:8080/login

输入账号 bcrypt
,密码 1234
,登录成功
