Spring Security加密解密

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: Spring Security加密解密

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之后这个类已经被标记为过时了,因为NoOpPasswordEncoderencode方法就只是简单地把字符序列转成字符串,不安全

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 接口,还需要实现该接口中的两个方法:

  1. encode 方法表示对密码进行加密,参数 rawPassword 就是你传入的明文密码,返回的则是加密之后的密文,这里的加密方案采用了 MD5。
  2. 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);
}

 

从这段代码中我们可以看到:

  1. 首先去调用 getBeanOrNull 方法获取一个 PasswordEncoder 实例,getBeanOrNull 方法实际上就是去 Spring 容器中查找对象。
  2. 接下来直接 new 一个 DaoAuthenticationProvider 对象,大家知道,在 new 的过程中,DaoAuthenticationProvider 中默认的 PasswordEncoder 已经被创建出来了。
  3. 如果一开始从 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() {}
}

 

可以看到:

  1. 在 PasswordEncoderFactories 中,首先构建了一个 encoders,然后给所有的编码方式都取了一个名字,再把名字做 key,编码方式做 value,统统存入 encoders 中。
  2. 最后返回了一个 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 + "\"");
  }
 }
}
  1. DelegatingPasswordEncoder 也是实现了 PasswordEncoder 接口,所以它里边的核心方法也是两个:encode 方法用来对密码进行编码,matches 方法用来校验密码。
  2. 在 DelegatingPasswordEncoder 的构造方法中,通过 通过传入的两个参数 encodingId 和 encoders ,获取到默认的编码器赋值给 passwordEncoderForEncode,默认的编码器实际上就是 BCryptPasswordEncoder。
  3. 在 encode 方法中对密码进行编码,但是编码的方式加了前缀,前缀是 {编码器名称} ,例如如果你使用 BCryptPasswordEncoder 进行编码,那么生成的密码就类似 {bcrypt}$2a$10$oE39aG10kB/rFu2vQeCJTu/V/v4n6DRR0f8WyXRiAYvBpmadoOBE.。这样有什么用呢?每种密码加密之后,都会加上一个前缀,这样看到前缀,就知道该密文是使用哪个编码器生成的了。
  4. 最后 matches 方法的逻辑就很清晰了,先从密文中提取出来前缀,再根据前缀找到对应的 PasswordEncoder,然后再调用 PasswordEncoder 的 matches 方法进行密码比对。
  5. 如果根据提取出来的前缀,找不到对应的 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种不同的密码加密方案的效果。


相关文章
|
4月前
|
安全 Java 数据安全/隐私保护
Spring Boot中的数据加密与解密
Spring Boot中的数据加密与解密
|
1月前
|
存储 安全 Java
|
4月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
4月前
|
安全 Java 数据库
实现基于Spring Security的权限管理系统
实现基于Spring Security的权限管理系统
|
4月前
|
安全 Java 数据安全/隐私保护
解析Spring Security中的权限控制策略
解析Spring Security中的权限控制策略
|
5月前
|
JSON 安全 Java
Spring Security 6.x 微信公众平台OAuth2授权实战
上一篇介绍了OAuth2协议的基本原理,以及Spring Security框架中自带的OAuth2客户端GitHub的实现细节,本篇以微信公众号网页授权登录为目的,介绍如何在原框架基础上定制开发OAuth2客户端。
214 4
Spring Security 6.x 微信公众平台OAuth2授权实战
|
4月前
|
编解码 安全 Java
如何在Spring Boot中实现数据加密
如何在Spring Boot中实现数据加密
|
4月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
4月前
|
存储 安全 Java
Spring Boot中的配置文件加密
Spring Boot中的配置文件加密
|
4月前
|
安全 Java 数据安全/隐私保护
使用Java和Spring Security实现身份验证与授权
使用Java和Spring Security实现身份验证与授权
下一篇
无影云桌面