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 security中哪一部分定义了这项功能?"
升级方案源码
首先我们得思考。什么情况下才会进行密码升级?
按照常理来说,应该是在用户登录成功之后进行密码升级。所以我们在找源码的时候,应该先去找认证成功的那部分源码,绝对能找到这部分功能。
我第一反应找UsernamePasswordAuthenticationFilter
的AbstractAuthenticationProcessingFilter
抽象类的doFilter
方法
但是不幸的是这里找不到我们想要的功能。所以我立即反应起来这项功能应该是在认证器这边。
DaoAuthenticationProvider
和AbstractUserDetailsAuthenticationProvider
找到的认证成功之后,他执行的一段函数。可以明显的看出有更新密码的过程。
这里只要保证upgradeEncoding == true
,那么就可以进入更新密码的过程。
这里我们看到了一段代码this.userDetailsPasswordService
, 可以百分百确定,我们的功能就在这个接口里面。
至于if的另一个函数upgradeEncoding
, 你只要知道用户输入密码和数据库密码ID不同就为 true, 相同就为 false, 当然还有ID相同不同长度的解决方案, 这里就不细谈了
public interface UserDetailsPasswordService { UserDetails updatePassword(UserDetails user, String newPassword); }
如果你英文能力比较强的话,可以直接去查看这个接口上面就会有注释,内容就是修改用户名的密码就这么简单。
既然已经知道这个接口的存在了,那现在的问题是怎么让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();
我们再回到这里看红框框的这行代码。
@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"
所以 idToPasswordEncoder
在 encode
函数时, 是BCryptPasswordEncoder
类
小白: "什么玩意儿, 乱七八糟的, 看不懂"
小黑: "抱歉表达能力不行, 我简单点说"
小黑: "因为PasswordEncoderFactories.createDelegatingPasswordEncoder()
函数使用bcrypt
作为默认加密方式, 所以在调用PasswordEncoder.encode
时默认也使用bcrypt
"
小黑: "还不懂就配合下面的图片看看"
造成它默认是BCryptPasswordEncoder
的原因是什么?
就上面这一行代码
搞懂这个有什么作用呢?
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升级密码加密的资料请关注我们其它相关文章!