认证
- 一、认证
- 1. 密码存储
- 二、Spring Security中的密码算法
- 1.PasswordEncoder
- 2.PasswordEncoder的实现类
- 3.DelegatingPasswordEncoder
- 4.PasswordEncoderFactories
- 4.自定义PasswordEncoder
一、认证
Spring Security 为身份验证提供了全面的支持。身份验证是我们验证尝试访问特定资源的人的身份的方式。验证用户身份的常用方法是要求用户输入用户名和密码。一旦执行身份验证,我们就知道身份并可以执行授权。
1. 密码存储
Spring Security 的 PasswordEncoder 接口用于执行密码的单向转换,以允许安全地存储密码。鉴于 PasswordEncoder 是一种单向转换,当密码转换需要两种方式(即存储用于对数据库进行身份验证的凭据)时,它不适合。通常,PasswordEncoder 用于存储需要在身份验证时与用户提供的密码进行比较的密码。
二、Spring Security中的密码算法
1.PasswordEncoder
PasswordEncoder是spring security定义的一个接口。接口中定义了三个方法:
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
- encode方法是用来对密码进行加密,返回密文。
- matches方法,验证从存储中获取的编码密码是否与提交的原始密码匹配后也被编码。 如果密码匹配,则返回 true,否则返回 false。 存储的密码本身永远不会被解码。
- upgradeEncoding方法,如果为了更好的安全性应该再次编码编码的密码,则返回 true,否则返回 false。 默认实现始终返回 false。
2.PasswordEncoder的实现类
3.DelegatingPasswordEncoder
在 Spring Security 5.0 之前,默认值PasswordEncoder是NoOpPasswordEncoder需要纯文本密码。但纯文本是不安全的,只能用来测试使用,希望默认PasswordEncoder值现在类似于BCryptPasswordEncoder. 然而,这忽略了三个现实世界的问题:
- 有许多使用旧密码编码的应用程序无法轻松迁移
- 密码存储的最佳做法将再次改变。
- 作为一个框架,Spring Security 不能频繁地进行重大更改
相反,Spring Security 引入了DelegatingPasswordEncoder它通过以下方式解决了所有问题:
- 确保使用当前的密码存储建议对密码进行编码
- 允许验证现代和传统格式的密码
- 允许将来升级编码
首先通过源码,看一下DelegatingPasswordEncoder维护了哪些属性:
private static final String PREFIX = "{";
private static final String SUFFIX = "}";
// Encoder对应的id
private final String idForEncode;
// 实际的PasswordEncoder
private final PasswordEncoder passwordEncoderForEncode;
// 一个维护idForEncode和passwordEncoderForEncode的Map集合
private final Map<String, PasswordEncoder> idToPasswordEncoder;
// 若id没匹配到encoder,则默认实现一个内部类,encode和match的时候会抛出异常
private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
我们再看下委托类实现的encode和matches方法:
@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);
}
通过源码可以了解到:
encode方法是按照规则进行拼接加密后的密文:前缀 {} 包含了编码的方式再拼接上加密后的密码串。
matches方法是通过提供的密文,按照规则找到对应的idForEncode,再通过idForEncode在事先维护的map中找到对应的PasswordEncoder进行密码匹配。如果找不到,就使用UnmappedIdPasswordEncoder。
那么委托类中idToPasswordEncoder的Map集合是什么时候维护的呢?通过构造函数,我们可以找到PasswordEncoderFactories工厂类。
4.PasswordEncoderFactories
PasswordEncoderFactories用于创建PasswordEncoder实例。该工厂类提供了一个静态方法createDelegatingPasswordEncoder。
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);
}
此时,结合前面我们自定义实现UserDetailsManager时,创建用户密码时,使用{noop}1234,目的就是验证密码时,使用纯文本PasswordEncoder进行验证。
4.自定义PasswordEncoder
若想指定PasswordEncoder,只需要显示的配置Encoder:
@Bean
public static PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
此时,我们便指定了纯文本的Encoder。