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

作者:bangiao 时间:2023-09-02 08:47:31 

本章内容

  • 密码加密方式怎么升级?

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

密码加密方式怎么升级?

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

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

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

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

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

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

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

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

小白: "那在spring security中哪一部分定义了这项功能?"

升级方案源码

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

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

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

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

DaoAuthenticationProviderAbstractUserDetailsAuthenticationProvider

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

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

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

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

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

public interface UserDetailsPasswordService {
  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 {
Optional<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.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);

是不是相当于

this.idForEncode = "bcrypt";
this.passwordEncoderForEncode = 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的时候才会用到

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

来源:https://juejin.cn/post/7182607268985176124

标签:Spring,Security,升级加密,密码加密
0
投稿

猜你喜欢

  • Kotlin与java8的SAM转换对比(进阶)

    2023-05-15 11:50:35
  • Java学习-打印1-1000以内的水仙花数代码实例

    2023-05-02 03:58:45
  • 详解利用SpringCloud搭建一个最简单的微服务框架

    2023-08-21 04:24:32
  • springboot+swagger2.10.5+mybatis-plus 入门详解

    2023-03-15 01:58:38
  • Java回调函数原理实例与代理模式的区别讲解

    2023-04-16 21:21:01
  • C#实现对二维数组排序的方法

    2023-06-19 09:09:08
  • Java实现FTP批量大文件上传下载篇2

    2021-07-22 02:30:31
  • 使用springboot跳转到指定页面和(重定向,请求转发的实例)

    2021-10-21 11:09:24
  • C#实现读写ini文件类实例

    2023-09-06 18:47:00
  • 深入解析java中的静态代理与动态代理

    2023-08-23 13:10:56
  • java和Spring中观察者模式的应用详解

    2023-04-21 00:16:17
  • Android监听键盘状态获取键盘高度的实现方法

    2023-12-02 16:44:17
  • Flutter生命周期超详细讲解

    2022-12-31 04:45:57
  • Java开发微信公众号接收和被动回复普通消息

    2022-04-07 12:30:44
  • 解决Callable的对象中,用@Autowired注入别的对象失败问题

    2023-11-29 13:23:46
  • Spring Cache+Redis缓存数据的实现示例

    2023-11-26 11:53:20
  • 简单了解Java synchronized关键字同步

    2022-07-25 10:38:05
  • java 可变参数详解及实例

    2021-06-01 10:02:42
  • 浅谈Android的Lifecycle源码分析

    2023-03-08 21:42:04
  • C#中HttpWebRequest的用法详解

    2023-06-18 22:39:27
  • asp之家 软件编程 m.aspxhome.com