一. 密码加密简介
1. 散列加密概述
我们开发时进行密码加密,可用的加密手段有很多,比如对称加密、非对称加密、信息摘要等。在一般的项目里,常用的就是信息摘要算法,也可以被称为散列加密函数,或者称为散列算法、哈希函数。这是一种可以从任何数据中创建数字“指纹”的方法,常用的散列函数有 MD5 消息摘要算法、安全散列算法(Secure Hash Algorithm)等。
2. 散列加密原理
散列函数通过把消息或数据压缩成摘要信息,使得数据量变小,将数据的格式固定下来,然后将数据打乱混合,再重新创建成一个散列值,从而达到加密的目的。散列值通常用一个短的随机字母和数字组成的字符串来代表,一个好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理时,如果我们不抑制冲突来区别数据,会使得数据库中的记录很难找到。
但是仅仅使用散列函数还不够,如果我们只是单纯的使用散列函数而不做特殊处理,其实是有风险的!比如在两个用户密码明文相同时,生成的密文也会相同,这样就增加了密码泄漏的风险。
所以为了增加密码的安全性,一般在密码加密过程中还需要“加盐”,而所谓的“盐”可以是一个随机数,也可以是用户名。”加盐“之后,即使密码的明文相同,用户生成的密码密文也不相同,这就可以极大的提高密码的安全性。
传统的加盐方式需要在数据库中利用专门的字段来记录盐值,这个字段可以是用户名字段(因为用户名唯一),也可以是一个专门记录盐值的字段,但这样的配置比较繁琐。
二、SpringSecurity 中的密码源码分析
当我们项目只引入springsecurity依赖之后,接下来什么事情都不用做,我们直接来启动项目。
在项目启动过程中,我们会看到如下一行日志:
Using generated security password: 10abfb2j-36e1-446a-jh9b-f70024fc89ab
这就是 Spring Security 为默认用户 user 生成的临时密码,是一个 UUID 字符串。这个密码和用户相关的自动化配置类在 UserDetailsServiceAutoConfiguration
里边,在该类的 getOrDeducePassword
方法中,我们看到如下一行日志:
if (user.isPasswordGenerated()) {
logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
}
毫无疑问,我们在控制台看到的日志就是从这里打印出来的。打印的条件是 isPasswordGenerated 方法返回 true,即密码是默认生成的。
进而我们发现,user.getPassword 出现在 SecurityProperties 中,在 SecurityProperties 中我们看到如下定义:
/**
* Default user name.
*/
private String name = "user";
/**
* Password for the default user name.
*/
private String password = UUID.randomUUID().toString();
private boolean passwordGenerated = true;
可以看到,默认的用户名就是 user,默认的密码则是 UUID,而默认情况下,passwordGenerated 也为 true。
SecurityProperties默认的用户就定义在它里边,是一个静态内部类,我们如果要定义自己的用户名密码,必然是要去覆盖默认配置,我们先来看下 SecurityProperties 的定义:
@ConfigurationProperties(prefix = "spring.security")
publicclass SecurityProperties {}
这就很清晰了,我们只需要以 spring.security.user 为前缀,去定义用户名密码即可:
spring.security.user.name=admin
spring.security.user.password=123456
这就是我们新定义的用户名密码。
在 properties 中定义的用户名密码最终是通过 set 方法注入到属性中去的,这里我们顺便来看下 SecurityProperties.User#setPassword 方法:
public void setPassword(String password) {
if (!StringUtils.hasLength(password)) {
return;
}
this.passwordGenerated = false;
this.password = password;
}
从这里我们可以看到,application.properties 中定义的密码在注入进来之后,还顺便设置了 passwordGenerated 属性为 false,这个属性设置为 false 之后,控制台就不会打印默认的密码了。
此时重启项目,就可以使用自己定义的用户名/密码登录了
除了上面的配置文件这种方式之外,我们也可以在配置类中配置用户名/密码。
@Configuration
publicclass SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("123456").roles("admin");
}
}
在配置类中配置,我们就要指定 PasswordEncoder 了,这是一个非常关键的东西
三、PasswordEncoder
1、PasswordEncoder
security中用于加密的接口就是PasswordEncoder,接口用于执行密码的单向转换,以便安全地存储密码,源码如下
public interface PasswordEncoder {
//该方法提供了明文密码的加密处理,加密后密文的格式主要取决于PasswordEncoder接口实现类实例。
String encode(CharSequence rawPassword);
//匹配存储的密码以及登录时传递的密码(登录密码是经过加密处理后的字符串)是否匹配,如果匹配该方法则会返回true,第一个参数表示需要被解析的密码 第二个参数表示存储的密码
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
PasswordEncoder 中的 encode 方法是我们在用户注册的时候手动调用,而matches 方法,则是由系统调用,默认是在 DaoAuthenticationProvider#additionalAuthenticationChecks 方法中调用的。
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
可以看到,密码比对就是通过 passwordEncoder.matches 方法来进行的。
Spring Security 提供了多种密码加密方案,官方推荐使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 强哈希函数,开发者在使用时可以选择提供 strength 和 SecureRandom 实例。strength 越大,密钥的迭代次数越多,密钥迭代次数为 2^strength。strength 取值在 4~31 之间,默认为 10。
不同于 Shiro 中需要自己处理密码加盐,在 Spring Security 中,BCryptPasswordEncoder 就自带了盐,处理起来非常方便。而 BCryptPasswordEncoder 就是 PasswordEncoder 接口的实现类。其他实现类列表如下
举例使用
Spring Security 5.0之前默认的PasswordEncoder实现类,即默认的加密方案是NoOpPasswordEncoder,5之后这个类已经被标记为过时了,因为NoOpPasswordEncoder的encode方法就只是简单地把字符序列转成字符串,不安全
2、codec
commons-codec 是一个 Apache 上的开源项目,用它可以方便的实现密码加密。在 Spring Security 还未推出 BCryptPasswordEncoder 的时候,commons-codec 还是一个比较常见的解决方案。先来介绍下 commons-codec 的用法。首先我们需要引入 commons-codec 的依赖:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
然后自定义一个 PasswordEncoder:
@Component
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes()));
}
}
在 Spring Security 中,PasswordEncoder 专门用来处理密码的加密与比对工作,我们自定义 MyPasswordEncoder 并实现 PasswordEncoder 接口,还需要实现该接口中的两个方法:
-
encode 方法表示对密码进行加密,参数 rawPassword 就是你传入的明文密码,返回的则是加密之后的密文,这里的加密方案采用了 MD5。
-
matches 方法表示对密码进行比对,参数 rawPassword 相当于是用户登录时传入的密码,encodedPassword 则相当于是加密后的密码(从数据库中查询而来)。
最后记得将 MyPasswordEncoder 通过 @Component 注解标记为 Spring 容器中的一个组件。
这样用户在登录时,就会自动调用 matches 方法进行密码比对。
当然,使用了 MyPasswordEncoder 之后,在用户注册时,就需要将密码加密之后存入数据库中,方式如下:
public int reg(User user) {
...
//插入用户,插入之前先对密码进行加密
user.setPassword(passwordEncoder.encode(user.getPassword()));
result = userMapper.reg(user);
...
}
3、BCryptPasswordEncoder
但是自己定义 PasswordEncoder 还是有些麻烦,特别是处理密码加盐问题的时候。
所以在 Spring Security 中提供了 BCryptPasswordEncoder,使得密码加密加盐变得非常容易。只需要提供 BCryptPasswordEncoder 这个 Bean 的实例即可,
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
创建 BCryptPasswordEncoder 时传入的参数 10 就是 strength,即密钥的迭代次数(也可以不配置,默认为 10)。同时,配置的内存用户的密码也不再是 123456 了,如下:
auth.inMemoryAuthentication()
.withUser("admin")
.password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
.roles("ADMIN", "USER")
.and()
.withUser("user")
.password("$2a$10$eUHbAOMq4bpxTvOVz33LIehLe3fu6NwqC9tdOcxJXEhyZ4simqXTC")
.roles("USER");
这里的密码就是使用 BCryptPasswordEncoder 加密后的密码,虽然 admin 和 user加密后的密码不一样,但是明文都是 123。配置完成后,使用 admin/123 或者 user/123 就可以实现登录。
本案例使用了配置在内存中的用户,一般情况下,用户信息是存储在数据库中的,因此需要在用户注册时对密码进行加密处理,如下:
@Service
public class RegService {
public int reg(String username, String password) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
String encodePasswod = encoder.encode(password);
return saveToDb(username, encodePasswod);
}
}
用户将密码从前端传来之后,通过调用 BCryptPasswordEncoder 实例中的 encode 方法对密码进行加密处理,加密完成后将密文存入数据库
4、多种加密方案共存
在 Spring Security 中,跟密码加密/校验相关的事情,都是由 PasswordEncoder 来主导的,PasswordEncoder 拥有众多的实现类,这些实现类,有的已经过期了,有的用处不大。对于我们而言,最常用的莫过于 BCryptPasswordEncoder。对于我们开发者而言,我们通常都是在 SecurityConfig 中配置一个 PasswordEncoder 的实例,类似下面这样:
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
剩下的事情,都是由系统调用的。今天我们就来揭开系统调用的神秘面纱!我们一起来看下系统到底是怎么调用的!Spring Security 中,如果使用用户名/密码的方式登录,密码是在DaoAuthenticationProvider 中进行校验的
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
可以看到,密码校验就是通过 passwordEncoder.matches 方法来完成的。
那么 DaoAuthenticationProvider 中的 passwordEncoder 从何而来呢?是不是就是我们一开始在 SecurityConfig 中配置的那个 Bean 呢?
我们来看下 DaoAuthenticationProvider 中关于 passwordEncoder 的定义,如下:
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private PasswordEncoder passwordEncoder;
public DaoAuthenticationProvider() {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return passwordEncoder;
}
}
从这段代码中可以看到,在 DaoAuthenticationProvider 创建之时,就指定了 PasswordEncoder,似乎并没有用到我们一开始配置的 Bean?其实不是的!在 DaoAuthenticationProvider 创建之时,会制定一个默认的 PasswordEncoder,如果我们没有配置任何 PasswordEncoder,将使用这个默认的 PasswordEncoder,如果我们自定义了 PasswordEncoder 实例,那么会使用我们自定义的 PasswordEncoder 实例!
从何而知呢?
我们再来看看 DaoAuthenticationProvider 是怎么初始化的。
DaoAuthenticationProvider 的初始化是在 InitializeUserDetailsManagerConfigurer#configure 方法中完成的,我们一起来看下该方法的定义:
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
UserDetailsService userDetailsService = getBeanOrNull(
UserDetailsService.class);
if (userDetailsService == null) {
return;
}
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
provider.afterPropertiesSet();
auth.authenticationProvider(provider);
}
从这段代码中我们可以看到:
-
首先去调用 getBeanOrNull 方法获取一个 PasswordEncoder 实例,getBeanOrNull 方法实际上就是去 Spring 容器中查找对象。
-
接下来直接 new 一个 DaoAuthenticationProvider 对象,大家知道,在 new 的过程中,DaoAuthenticationProvider 中默认的 PasswordEncoder 已经被创建出来了。
-
如果一开始从 Spring 容器中获取到了 PasswordEncoder 实例,则将之赋值给 DaoAuthenticationProvider 实例,否则就是用 DaoAuthenticationProvider 自己默认创建的 PasswordEncoder。
至此,就真相大白了,我们配置的 PasswordEncoder 实例确实用上了
同时大家看到,如果我们不进行任何配置,默认的 PasswordEncoder 也会被提供,那么默认的 PasswordEncoder 是什么呢?我们就从这个方法看起:
public DaoAuthenticationProvider() {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
PasswordEncoderFactories类如下
public 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);
}
private PasswordEncoderFactories() {}
}
可以看到:
-
在 PasswordEncoderFactories 中,首先构建了一个 encoders,然后给所有的编码方式都取了一个名字,再把名字做 key,编码方式做 value,统统存入 encoders 中。
-
最后返回了一个 DelegatingPasswordEncoder 实例,同时传入默认的 encodingId 就是 bcrypt,以及 encoders 实例,DelegatingPasswordEncoder 看名字应该是一个代理对象。
我们来看下 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();
public DelegatingPasswordEncoder(String idForEncode,
Map<String, PasswordEncoder> idToPasswordEncoder) {
if (idForEncode == null) {
throw new IllegalArgumentException("idForEncode cannot be null");
}
if (!idToPasswordEncoder.containsKey(idForEncode)) {
throw new IllegalArgumentException("idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);
}
for (String id : idToPasswordEncoder.keySet()) {
if (id == null) {
continue;
}
if (id.contains(PREFIX)) {
throw new IllegalArgumentException("id " + id + " cannot contain " + PREFIX);
}
if (id.contains(SUFFIX)) {
throw new IllegalArgumentException("id " + id + " cannot contain " + SUFFIX);
}
}
this.idForEncode = idForEncode;
this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder);
}
public void setDefaultPasswordEncoderForMatches(
PasswordEncoder defaultPasswordEncoderForMatches) {
if (defaultPasswordEncoderForMatches == null) {
throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null");
}
this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches;
}
@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);
}
private String extractId(String prefixEncodedPassword) {
if (prefixEncodedPassword == null) {
return null;
}
int start = prefixEncodedPassword.indexOf(PREFIX);
if (start != 0) {
return null;
}
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
if (end < 0) {
return null;
}
return prefixEncodedPassword.substring(start + 1, end);
}
@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);
}
}
private String extractEncodedPassword(String prefixEncodedPassword) {
int start = prefixEncodedPassword.indexOf(SUFFIX);
return prefixEncodedPassword.substring(start + 1);
}
private class UnmappedIdPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
throw new UnsupportedOperationException("encode is not supported");
}
@Override
public boolean matches(CharSequence rawPassword,
String prefixEncodedPassword) {
String id = extractId(prefixEncodedPassword);
throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");
}
}
}
-
DelegatingPasswordEncoder 也是实现了 PasswordEncoder 接口,所以它里边的核心方法也是两个:encode 方法用来对密码进行编码,matches 方法用来校验密码。
-
在 DelegatingPasswordEncoder 的构造方法中,通过 通过传入的两个参数 encodingId 和 encoders ,获取到默认的编码器赋值给 passwordEncoderForEncode,默认的编码器实际上就是 BCryptPasswordEncoder。
-
在 encode 方法中对密码进行编码,但是编码的方式加了前缀,前缀是
{编码器名称}
,例如如果你使用 BCryptPasswordEncoder 进行编码,那么生成的密码就类似{bcrypt}$2a$10$oE39aG10kB/rFu2vQeCJTu/V/v4n6DRR0f8WyXRiAYvBpmadoOBE.
。这样有什么用呢?每种密码加密之后,都会加上一个前缀,这样看到前缀,就知道该密文是使用哪个编码器生成的了。 -
最后 matches 方法的逻辑就很清晰了,先从密文中提取出来前缀,再根据前缀找到对应的 PasswordEncoder,然后再调用 PasswordEncoder 的 matches 方法进行密码比对。
-
如果根据提取出来的前缀,找不到对应的 PasswordEncoder,那么就会调用 UnmappedIdPasswordEncoder#matches 方法,进行密码比对,该方法实际上并不会进行密码比对,而是直接抛出异常。
OK,至此,明白了 DelegatingPasswordEncoder 的工作原理。
如果我们想同时使用多个密码加密方案,使用 DelegatingPasswordEncoder 就可以了,而 DelegatingPasswordEncoder 默认还不用配置。Security 5之后用的默认加密方案实现类就是DelegatingPasswordEncoder
我们进行开发时,经常需要对老旧项目进行改造。这个老旧项目,一开始用的密码加密方案可能是MD5,后来因为种种原因,可能会觉得这个MD5加密不合适,想更新替换一种新的加密方案。但是我们进行项目开发时,密码加密方式一旦确定,基本上没法再改了,毕竟我们不能让用户重新注册再设置一次新密码吧。但是我们此时确实又想使用最新的密码加密方案,那怎么办呢?
这时候,我们就可以考虑使用DelegatingPasswordEncoder来实现多密码加密方案了!
首先配置DelegatingPasswordEncoder对象(不配置也行 默认就是DelegatingPasswordEncoder)
@Bean
public PasswordEncoder passwordEncoder() {
/*
*利用工厂类PasswordEncoderFactories实现,工厂类内部采用的是委派密码编码方案!
*推荐使用该方案,因为后期可以实现多密码加密方案共存效果!
/
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
测试接口
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserMapper userMapper;
@GetMapping("hello")
public String hello() {
return "hello, user";
}
/**
* 采用默认的PasswordEncoder,即BCryptPasswordEncoder来加密。
*
* 添加用户.这里我们采用表单形式传参,传参形式如下:
* http://localhost:8080/user/register?username=test&password=123
*/
@GetMapping("/register")
public User registerUser(@RequestParam(required = false) User user) {
user.setEnable(true);
user.setRoles("ROLE_ADMIN");
//对密码进行加密
user.setPassword(passwordEncoder.encode(user.getPassword()));
userMapper.addUser(user);
return user;
}
/**
* 利用MD5加密密码
*/
@GetMapping("/registerMd5")
public User registerUserWithMd5(@RequestParam(required = false, name = "username") String username, @RequestParam(required = false, name = "password") String password) {
User user = new User();
user.setUsername(username);
user.setEnable(true);
user.setRoles("ROLE_ADMIN");
Map<String, PasswordEncoder> encoders = new HashMap<>(16);
//encoders.put("bcrypt", new BCryptPasswordEncoder());
//encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
DelegatingPasswordEncoder md5Encoder = new DelegatingPasswordEncoder("MD5", encoders);
//对密码进行加密
user.setPassword(md5Encoder.encode(password));
userMapper.addUser(user);
return user;
}
/**
* 不进行密码加密
*/
@GetMapping("/registerNoop")
public User registerUserWithNoop(@RequestParam(required = false, name = "username") String username, @RequestParam(required = false, name = "password") String password) {
User user = new User();
user.setUsername(username);
user.setEnable(true);
user.setRoles("ROLE_ADMIN");
Map<String, PasswordEncoder> encoders = new HashMap<>(16);
//encoders.put("bcrypt", new BCryptPasswordEncoder());
//encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", NoOpPasswordEncoder.getInstance());
DelegatingPasswordEncoder noopEncoder = new DelegatingPasswordEncoder("noop", encoders);
//对密码进行加密
user.setPassword(noopEncoder.encode(password));
userMapper.addUser(user);
return user;
}
}
记得配置类中对上三个接口放行,浏览器中分别请求以上的3个接口,添加3个用户
我的数据库中,此时就会有3个采用不同加密方案的用户了。
然后我们可以分别利用这三个用户进行登录,可以发现在同一个项目中,实现了支持3种不同的密码加密方案的效果。