简介
DelegatingPasswordEncoder
的中文含义是"委托密码编码器"。其中:
- Delegating:意为"委托",表示该类将密码的编码和验证操作委托给不同的具体密码编码器。
- PasswordEncoder:密码编码器,用于对密码进行加密和验证。
因此,
DelegatingPasswordEncoder
是一个委托机制的密码编码器,通过它可以动态选择不同的编码器来处理密码的编码与验证。
DelegatingPasswordEncoder
是 Spring Security 提供的一个类,用于处理密码编码和解码的逻辑。它的主要作用是支持多种密码编码器,并能够根据密码的存储格式自动选择使用哪种编码器。这在处理用户密码的存储和验证时非常有用,特别是在系统需要迁移或同时支持不同的编码格式时。
主要特点
- 多种编码器支持 :
DelegatingPasswordEncoder
可以配置多个密码编码器,并根据密码前缀来决定使用哪个编码器。例如,你可以同时支持 BCrypt、PBKDF2、SHA 等多种编码算法。 - 密码格式识别 :存储密码时,通常会在密码前加上编码算法的标识符(前缀),
DelegatingPasswordEncoder
会读取这个前缀并选择相应的编码器进行解码或验证。 - 灵活的算法配置 :在配置
DelegatingPasswordEncoder
时,可以指定默认编码器和其他编码器的映射关系,使得系统可以灵活应对密码编码的变化。
示例代码
以下是一个简单的示例,展示如何使用 DelegatingPasswordEncoder
:
java
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.HashMap;
import java.util.Map;
public class PasswordEncoderExample {
public static void main(String[] args) {
// 创建密码编码器映射
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder());
// 可以添加其他编码器,如 PBKDF2, SCrypt 等
// 创建 DelegatingPasswordEncoder,并指定默认编码器
PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder("bcrypt", encoders);
// 编码密码
String rawPassword = "mysecretpassword";
String encodedPassword = passwordEncoder.encode(rawPassword);
System.out.println("Encoded Password: " + encodedPassword);
// 验证密码
boolean isMatch = passwordEncoder.matches(rawPassword, encodedPassword);
System.out.println("Password Match: " + isMatch);
}
}
控制台输出:
swift
Encoded Password: {bcrypt}$2a$10$aeGTTVo/c8panHFB/KDxE.3iPWK.Znyu5rneraR3ultUDlNA10LpK
Password Match: true
核心方法
构造函数
两个关键参数:
idForEncode
:它指定了用于编码新密码的默认编码器的 IDidToPasswordEncoder
:它是一个Map
,包含各种编码器的映射,键是编码器的 ID,值是相应的PasswordEncoder
实例。
typescript
/**
* 创建一个新的实例
* @param idForEncode 用于查找哪种 {@link PasswordEncoder} 将被用于 {@link #encode(CharSequence)} 的 ID
* @param idToPasswordEncoder 一个 ID 到 {@link PasswordEncoder} 的映射,用于确定
* 哪种 {@link PasswordEncoder} 应该用于 {@link #matches(CharSequence, String)}
*/
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
// 调用另一个构造函数,并传入默认的前缀和后缀
this(idForEncode, idToPasswordEncoder, DEFAULT_ID_PREFIX, DEFAULT_ID_SUFFIX);
}
/**
* 创建一个新的实例
* @param idForEncode 用于查找哪种 {@link PasswordEncoder} 将被用于 {@link #encode(CharSequence)} 的 ID
* @param idToPasswordEncoder 一个 ID 到 {@link PasswordEncoder} 的映射,用于确定
* 哪种 {@link PasswordEncoder} 应该用于 {@link #matches(CharSequence, String)}
* @param idPrefix 表示编码结果中 ID 开始的前缀
* @param idSuffix 表示编码结果中 ID 结束的后缀
*/
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder,
String idPrefix, String idSuffix) {
// 检查用于编码的ID是否为null
if (idForEncode == null) {
throw new IllegalArgumentException("idForEncode cannot be null");
}
// 检查前缀是否为null
if (idPrefix == null) {
throw new IllegalArgumentException("prefix cannot be null");
}
// 检查后缀是否为null或空
if (idSuffix == null || idSuffix.isEmpty()) {
throw new IllegalArgumentException("suffix cannot be empty");
}
// 确保前缀不包含后缀
if (idPrefix.contains(idSuffix)) {
throw new IllegalArgumentException("idPrefix " + idPrefix + " cannot contain idSuffix " + idSuffix);
}
// 检查指定的编码ID是否存在于编码器映射中
if (!idToPasswordEncoder.containsKey(idForEncode)) {
throw new IllegalArgumentException(
"idForEncode " + idForEncode + " is not found in idToPasswordEncoder " + idToPasswordEncoder);
}
// 遍历所有编码器ID,确保它们不包含前缀或后缀
for (String id : idToPasswordEncoder.keySet()) {
if (id == null) {
continue; // 跳过null值的ID
}
// 检查ID是否包含前缀
if (!idPrefix.isEmpty() && id.contains(idPrefix)) {
throw new IllegalArgumentException("id " + id + " cannot contain " + idPrefix);
}
// 检查ID是否包含后缀
if (id.contains(idSuffix)) {
throw new IllegalArgumentException("id " + id + " cannot contain " + idSuffix);
}
}
// 初始化类成员变量
this.idForEncode = idForEncode; // 设置用于编码的ID
this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode); // 获取对应的PasswordEncoder
this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder); // 创建ID到PasswordEncoder的映射副本
this.idPrefix = idPrefix; // 设置前缀
this.idSuffix = idSuffix; // 设置后缀
}
encode
方法
该方法负责对原始密码进行编码。它根据构造函数中指定的编码器 ID,调用相应的 PasswordEncoder
实现来对密码进行编码,并在密码前加上指定的前缀和后缀。
kotlin
@Override
public String encode(CharSequence rawPassword) {
return this.idPrefix + this.idForEncode + this.idSuffix + this.passwordEncoderForEncode.encode(rawPassword);
}
matches
方法
该方法用来验证原始密码与编码后的密码是否匹配。它提取编码密码中的 ID,并根据这个 ID 查找对应的 PasswordEncoder
,然后调用该编码器的 matches
方法进行匹配。如果 ID 没有映射到任何编码器,则使用默认的密码编码器进行匹配。
kotlin
/**
* 验证给定的原始密码和已编码密码是否匹配。
*
* <p>该方法首先检查原始密码和编码密码是否都为 null。如果是,则返回 true,表示它们相等。
* 如果编码密码带有前缀(例如 {bcrypt} 或 {noop}),则从中提取编码器的 ID,并使用相应的密码编码器进行验证。
* 如果没有找到合适的编码器,则使用默认的编码器进行匹配。
*
* @param rawPassword 原始密码,即未编码的密码。
* @param prefixEncodedPassword 带有前缀的已编码密码,可能包含编码器的 ID。
* @return 如果密码匹配,则返回 true;否则返回 false。
*/
@Override
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
// 如果原始密码和已编码密码均为 null,则返回 true,表示它们匹配
if (rawPassword == null && prefixEncodedPassword == null) {
return true;
}
// 从已编码密码中提取编码器 ID(例如 {bcrypt} -> "bcrypt")
String id = extractId(prefixEncodedPassword);
// 根据提取出的 ID 获取对应的密码编码器
PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
// 如果没有找到对应的编码器,使用默认编码器进行匹配
if (delegate == null) {
return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);
}
// 从带前缀的已编码密码中提取实际的编码密码
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
// 使用找到的密码编码器进行匹配操作
return delegate.matches(rawPassword, encodedPassword);
}
upgradeEncoding
方法
此方法检查指定的编码密码是否使用了过时的编码格式。如果是,则返回 true,指示需要升级编码。否则,使用当前的编码器进行检查。
typescript
/**
* 判断是否需要对密码的编码进行升级。
*
* @param prefixEncodedPassword 带有前缀的加密密码,前缀通常用于标识该密码的编码方式。
* @return 如果需要对密码的编码进行升级,则返回 true;否则返回 false。
*/
@Override
public boolean upgradeEncoding(String prefixEncodedPassword) {
// 从带前缀的加密密码中提取出编码方式的标识符(id)
String id = extractId(prefixEncodedPassword);
// 检查当前系统使用的编码标识符是否与密码的编码标识符不同
// 如果不同,表示编码方式已过时,需要升级,返回 true
if (!this.idForEncode.equalsIgnoreCase(id)) {
return true;
}
else {
// 提取实际的加密密码,不包括前缀部分
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
// 根据提取出的编码标识符获取相应的密码编码器,并检查是否需要对密码的编码进行升级
return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword);
}
}