开发者

Spring Security如何实现升级密码加密方式详解

目录
  • 本章内容
  • 密码加密方式怎么升级?
  • 升级方案源码
  • 实战
    • 第一种方式: Spring Bean
      • 他是怎么自动升级到BCrypt加密方式的?
    • 第二种方式: 多继承接口方式
      • 第三种方式: HttpSecurity直接添加

      本章内容

      • 密码加密方式怎么升级?
      • spring security底层怎么实现的密码加密方式升级?

      密码加密方式怎么升级?

      前面我们学过DelegatingPasswordEncoder类,但是不清楚他到底是做什么的,我也没讲的很清楚。所以呢,我们就重新再讲一讲它的另一个实际应用。

      小明呢,有一天在刷新闻。突然收到了一篇关于MD5加密存在重大漏洞的报告, 而最佳的代替加密方案是BCrypt。此时小明慌了。

      因为他项目里面就是用着MD5加密。那现在怎么办呢?小明的用户体量比较大,你不可能叫客户/程序员一个个去改是吧?

      spring security就提供了一种这种情况的解决方案。

      在用户登录你的账户时,自动的升级您的密码加密方式。比如说从MD5加密方式变成BCrypt

      但是呢,这种方式有一个前提。您数据库的用户密码必须要有ID,也就是花括号的那一部分{noop}123456

      当然如果花括号没有,然后数据体量就比较大,你只能重写DelegatingPasswordEncoder

      抄代码的地方就在PasswordEncoderFactories#createDelegatingPasswordEncoder, 也就是你数据库中的密码没有花括号部分(拿不到ID)的情况下, 使用BCryptPasswordEncoder

      小白: "那在spring securit编程客栈y中哪一部分定义了这项功能?"

      升级方案源码

      首先我们得思考。什么情况下才会进行密码升级?

      按照常理来说,应该是在用户登录成功之后进行密码升级。所以我们在找源码的时候,应该先去找认证成功的那部分源码,绝对能找到这部分功能。

      我第一反应找UsernamePasswordAuthenticationFilterAbstractAuthenticationProcessingFilter抽象类的doFilter方法

      但是不幸的是这里找不到我们想要的功能。所以我立即反应起来这项功能应该是在认证器这边。

      DaoAuthenticationProviderAbstractUserDetailsAuthenticationProvider

      Spring Security如何实现升级密码加密方式详解

      找到的认证成功之后,他执行的一段函数。可以明显的看出有更新密码的过程。

      这里只要保证upgradeEncoding == true,那么就可以进入更新密码的过程。

      这里我们看到了一段代码this.userDetailsPasswordService, 可以百分百确定,我们的功能就在这个接口里面。

      至于if的另一个函数upgradeEncoding, 你只要知道用户输入密码和数据库密码ID不同就为 true, 相同就为 false, 当然还有ID相同不同长度的解决方案, 这里就不细谈了

      public interface UserDetailsPasswordS开发者_JAVAervice {
         UserDetails updatePassword(UserDetails user, String newpassword);
      }
      

      如果你英文能力比较强的话,可以直接去查看这个接口上面就会有注释,内容就是修改用户名的密码就这么简单。

      既然已经知道这个接口的存在了,那现在的问题是怎么让spring security调用我们所实现的这个接口呢?

      我现在罗列出三张图片。就可以从这三张图片中总结出三种加载我们实现类的方法。

      Spring Security如何实现升级密码加密方式详解

      Spring Security如何实现升级密码加密方式详解

      Spring Security如何实现升级密码加密方式详解

      实战

      第一种方式: Spring Bean

      public class UserService1 implements UserDetailsService {
      	@Resource
      	private UsersMapper usersMapper;
      	@Override
      	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException www.devze.com{
      		Optional&amhttp://www.devze.comp;lt;Users> optionalUsers = Optional.ofNullable(usersMapper.loadUserByUsername(username));
      		return optionalUsers.orElseThrow(() -> new UsernameNotFoundException("找不到用户名"));
      	}
      }
      
      @Bean
      public UserService1 userService1() throws Exception {
          return new UserService1();
      }
      

      这种方式对应着上面第3张图。

      那现在就会有人问的。我并没有写出从MD5加密方式升级到BCrypt加密方式。他是怎么自动升级到BCrypt加密方式的?

      带着问题看源码

      他是怎么自动升级到BCrypt加密方式的?

      我们知道spring security里面默认使用的PasswordEncoder是这样的。

      @Bean
      public PasswordEncoder passwordEncoder() {
          return PasswordEncoderFactories.createDelegatingPasswordEncoder();
      }
      

      不知道当做知道哈

      他们内部的源码是这样的。

      public static PasswordEncoder createDelegatingPasswordEncoder() {
          // 省略了一堆代码
         String encodingId = "bcrypt";
         Map<String, PasswordEncoder> encoders = new HashMap<>();
         encoders.put(encodingId, new BCryptPasswordEncoder());
         encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
         encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
         return new DelegatingPasswordEncoder(encodingId, encoders);
      }
      

      嗯,你要注意这几行代码。

      String encodingId = "bcrypt";
      encoders.put(encodingId, new BCryptPasswordEncoder());
      return new DelegatingPasswordEncoder(encodingId, encoders);
      

      别的什么都不看,只看encodingId变量。我们现在进入DelegatingPasswordEncoder的内部看看他的构造函数。

      public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder,
            String idPrefix, String idSuffix) {
          // 省略一堆代码
         this.idForEncode = idForEncode;
         this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
         this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder);
         this.idPrefix = idPrefix;
         this.idSuffix = idSuffix;
      }
      

      encodingId在这个类中被叫做idForEncode

      了解了这个之后,再关注这几行代码。

      this.idForEncode = idForEncode;
      this.pjavascriptasswordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
      

      是不是相当于

      this.idForEncode = "bcrypt";
      this.passwo编程rdEncoderForEncode = new BCryptPasswordEncoder();
      

      我们再回到这里看红框框的这行代码。

      Spring Security如何实现升级密码加密方式详解

      @Override
      public String encode(CharSequence rawPassword) {
         return this.idPrefix + this.idForEncode + this.idSuffix + this.passwordEncoderForEncode.encode(rawPassword);
      }
      

      现在你对比一下这个函数跟前面构造函数的名字看看。

      构造函数的变量叫 idForEncode , encode函数也叫 idForEncode , 前面的构造函数,我们发现这个变量其实已经被保存在DelegatingPasswordEncoder类里面了。而且值还是"bcrypt"

      而构造函数里面this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode)

      idToPasswordEncoder就是个Map, k是每种加密对象的id, v是每种加密算法

      比如: key = "bcrypt", 那么 value = "BCryptPasswordEncoder"

      所以 idToPasswordEncoderencode 函数时, 是BCryptPasswordEncoder

      Spring Security如何实现升级密码加密方式详解

      小白: "什么玩意儿, 乱七八糟的, 看不懂"

      小黑: "抱歉表达能力不行, 我简单点说"

      小黑: "因为PasswordEncoderFactories.createDelegatingPasswordEncoder()函数使用bcrypt作为默认加密方式, 所以在调用PasswordEncoder.encode时默认也使用bcrypt"

      小黑: "还不懂就配合下面的图片看看"

      Spring Security如何实现升级密码加密方式详解

      造成它默认是BCryptPasswordEncoder的原因是什么?

      Spring Security如何实现升级密码加密方式详解

      就上面这一行代码

      搞懂这个有什么作用呢?

      Spring security默认全部加密方式升级方案全部都是bcrypt,那如果我们要自定义升级到我们需要的加密方式呢?

      重写PasswordEncoderFactories类, 把上面的变量修改成你需要修改的加密类型, 并且往Map中添加加密类型的对象

      public static PasswordEncoder createDelegatingPasswordEncoder() {
         String encodingId = "无敌加密";
         Map<String, PasswordEncoder> encoders = new HashMap<>();
         encoders.put(encodingId, new 无敌加密PasswordEncoder());
          // 省略一堆代码
         return new DelegatingPasswordEncoder(encodingId, encoders);
      }
      

      我去跑题了, 回归正题

      第二种方式: 多继承接口方式

      public class UserService implements UserDetailsService, UserDetailsPasswordService {
         @Resource
         private UsersMapper usersMapper;
         /**
          * 升级用户密码为当前加密方式
          *
          * @param user        要修改的用户, 这个用户必须有 id
          * @param newPassword 新的密码, 该密码已经被 passwordEncoder 加密
          * @return
          */
         @Override
         public UserDetails updatePassword(UserDetails user, String newPassword) {
            if (user instanceof Users users) {
               users.setPassword(newPassword);
               usersMapper.updateByPrimaryKeySelective(users);
            }
            return user;
         }
         @Override
         public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            Optional<Users> optionalUsers = Optional.ofNullable(usersMapper.loadUserByUsername(username));
            return optionalUsers.orElseThrow(() -> new UsernameNotFoundException("找不到用户"));
         }
      }
      

      这种方式对应着上面三张图片的第1张图片给出的方案

      第三种方式: HttpSecurity直接添加

      @Bean
      public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
         AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
         authenticationManagerBuilder.authenticationProvider(/* 你的认证七 */)
               .userDetailsService(/* 加载用户方式 */)
               .passwordEncoder(/* 密码加密方式 */)
               .userDetailsPasswordManager(/* 第三种更新加密的方式 */);
         return authenticationManagerBuilder.build();
      }
      

      这种方式比较麻烦, 只有你需要重写某个Provider的时候才会用到

      一般我们使用第二种方式就行

      以上就是Spring Security如何实现升级密码加密方式详解的详细内容,更多关于Spring Security升级密码加密的资料请关注我们其它相关文章!

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜