加入收藏 | 设为首页 | 会员中心 | 我要投稿 淮南站长网 (https://www.0554zz.cn/)- 管理运维、图像技术、智能营销、专属主机、5G!
当前位置: 首页 > 服务器 > 安全 > 正文

Spring Security 如何实现多种加密方案共存

发布时间:2022-06-29 13:34:55 所属栏目:安全 来源:互联网
导读:为什么要加密?常见的加密算法等等这些问题我就不再赘述了,大家可以参考之前的:Spring Boot 中密码加密的两种姿势!,咱们直接来看今天的正文。 1.PasswordEncoder 在 Spring Security 中,跟密码加密/校验相关的事情,都是由 PasswordEncoder 来主导的,Pas
  为什么要加密?常见的加密算法等等这些问题我就不再赘述了,大家可以参考之前的:Spring Boot 中密码加密的两种姿势!,咱们直接来看今天的正文。
 
  1.PasswordEncoder
  在 Spring Security 中,跟密码加密/校验相关的事情,都是由 PasswordEncoder 来主导的,PasswordEncoder 拥有众多的实现类:
 
 
 
  这些实现类,有的已经过期了,有的用处不大。对于我们而言,最常用的莫过于 BCryptPasswordEncoder。
 
  PasswordEncoder 本身是一个接口,里边只有三个方法:
 
  复制
  public interface PasswordEncoder {
   String encode(CharSequence rawPassword);
   boolean matches(CharSequence rawPassword, String encodedPassword);
   default boolean upgradeEncoding(String encodedPassword) {
    return false;
   }
  }
  1.
  2.
  3.
  4.
  5.
  6.
  7.
  encode 方法用来对密码进行加密。
  matches 方法用来对密码进行比对。
  upgradeEncoding 表示是否需要对密码进行再次加密以使得密码更加安全,默认为 false。
  PasswordEncoder 的实现类,则具体实现了这些方法。
 
  2.PasswordEncoder 在哪里起作用
  对于我们开发者而言,我们通常都是在 SecurityConfig 中配置一个 PasswordEncoder 的实例,类似下面这样:
 
  复制
  @Bean
  PasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
  }
  1.
  2.
  3.
  4.
  剩下的事情,都是由系统调用的。今天我们就来揭开系统调用的神秘面纱!我们一起来看下系统到底是怎么调用的!
 
  首先,松哥在前面的文章中和大家提到过,Spring Security 中,如果使用用户名/密码的方式登录,密码是在 DaoAuthenticationProvider 中进行校验的,大家可以参考:SpringSecurity 自定义认证逻辑的两种方式(高级玩法)。
 
  我们来看下 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 实例确实用上了。
 
  3.默认的是什么?
  同时大家看到,如果我们不进行任何配置,默认的 PasswordEncoder 也会被提供,那么默认的 PasswordEncoder 是什么呢?我们就从这个方法看起:
 
  复制
  public DaoAuthenticationProvider() {
   setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
  }
  1.
  2.
  3.
  继续:
 
  复制
  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 看名字应该是一个代理对象。

(编辑:淮南站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读