Spring?Security如何實現(xiàn)升級密碼加密方式詳解
本章內(nèi)容
- 密碼加密方式怎么升級?
- spring security底層怎么實現(xiàn)的密碼加密方式升級?
密碼加密方式怎么升級?
前面我們學(xué)過DelegatingPasswordEncoder類,但是不清楚他到底是做什么的,我也沒講的很清楚。所以呢,我們就重新再講一講它的另一個實際應(yīng)用。
小明呢,有一天在刷新聞。突然收到了一篇關(guān)于MD5加密存在重大漏洞的報告, 而最佳的代替加密方案是BCrypt。此時小明慌了。
因為他項目里面就是用著MD5加密。那現(xiàn)在怎么辦呢?小明的用戶體量比較大,你不可能叫客戶/程序員一個個去改是吧?
spring security就提供了一種這種情況的解決方案。
在用戶登錄你的賬戶時,自動的升級您的密碼加密方式。比如說從MD5加密方式變成BCrypt
但是呢,這種方式有一個前提。您數(shù)據(jù)庫的用戶密碼必須要有ID,也就是花括號的那一部分{noop}123456。
當然如果花括號沒有,然后數(shù)據(jù)體量就比較大,你只能重寫DelegatingPasswordEncoder。
抄代碼的地方就在PasswordEncoderFactories#createDelegatingPasswordEncoder, 也就是你數(shù)據(jù)庫中的密碼沒有花括號部分(拿不到ID)的情況下, 使用BCryptPasswordEncoder
小白: "那在spring security中哪一部分定義了這項功能?"
升級方案源碼
首先我們得思考。什么情況下才會進行密碼升級?
按照常理來說,應(yīng)該是在用戶登錄成功之后進行密碼升級。所以我們在找源碼的時候,應(yīng)該先去找認證成功的那部分源碼,絕對能找到這部分功能。
我第一反應(yīng)找UsernamePasswordAuthenticationFilter的AbstractAuthenticationProcessingFilter抽象類的doFilter方法
但是不幸的是這里找不到我們想要的功能。所以我立即反應(yīng)起來這項功能應(yīng)該是在認證器這邊。
DaoAuthenticationProvider和AbstractUserDetailsAuthenticationProvider

找到的認證成功之后,他執(zhí)行的一段函數(shù)。可以明顯的看出有更新密碼的過程。
這里只要保證upgradeEncoding == true,那么就可以進入更新密碼的過程。
這里我們看到了一段代碼this.userDetailsPasswordService, 可以百分百確定,我們的功能就在這個接口里面。
至于if的另一個函數(shù)upgradeEncoding, 你只要知道用戶輸入密碼和數(shù)據(jù)庫密碼ID不同就為 true, 相同就為 false, 當然還有ID相同不同長度的解決方案, 這里就不細談了
public interface UserDetailsPasswordService {
UserDetails updatePassword(UserDetails user, String newPassword);
}
如果你英文能力比較強的話,可以直接去查看這個接口上面就會有注釋,內(nèi)容就是修改用戶名的密碼就這么簡單。
既然已經(jīng)知道這個接口的存在了,那現(xiàn)在的問題是怎么讓spring security調(diào)用我們所實現(xiàn)的這個接口呢?
我現(xiàn)在羅列出三張圖片。就可以從這三張圖片中總結(jié)出三種加載我們實現(xiàn)類的方法。



實戰(zhàn)
第一種方式: 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();
}
這種方式對應(yīng)著上面第3張圖。
那現(xiàn)在就會有人問的。我并沒有寫出從MD5加密方式升級到BCrypt加密方式。他是怎么自動升級到BCrypt加密方式的?
帶著問題看源碼
他是怎么自動升級到BCrypt加密方式的?
我們知道spring security里面默認使用的PasswordEncoder是這樣的。
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
不知道當做知道哈
他們內(nèi)部的源碼是這樣的。
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變量。我們現(xiàn)在進入DelegatingPasswordEncoder的內(nèi)部看看他的構(gòu)造函數(shù)。
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
了解了這個之后,再關(guān)注這幾行代碼。
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);
}
現(xiàn)在你對比一下這個函數(shù)跟前面構(gòu)造函數(shù)的名字看看。
構(gòu)造函數(shù)的變量叫 idForEncode , encode函數(shù)也叫 idForEncode , 前面的構(gòu)造函數(shù),我們發(fā)現(xiàn)這個變量其實已經(jīng)被保存在DelegatingPasswordEncoder類里面了。而且值還是"bcrypt"
而構(gòu)造函數(shù)里面this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode)
idToPasswordEncoder就是個Map, k是每種加密對象的id, v是每種加密算法
比如: key = "bcrypt", 那么 value = "BCryptPasswordEncoder"
所以 idToPasswordEncoder 在 encode 函數(shù)時, 是BCryptPasswordEncoder類

小白: "什么玩意兒, 亂七八糟的, 看不懂"
小黑: "抱歉表達能力不行, 我簡單點說"
小黑: "因為PasswordEncoderFactories.createDelegatingPasswordEncoder()函數(shù)使用bcrypt作為默認加密方式, 所以在調(diào)用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 新的密碼, 該密碼已經(jīng)被 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("找不到用戶"));
}
}
這種方式對應(yīng)著上面三張圖片的第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如何實現(xiàn)升級密碼加密方式詳解的詳細內(nèi)容,更多關(guān)于Spring Security升級密碼加密的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決SpringMvc后臺接收json數(shù)據(jù)中文亂碼問題的幾種方法
本篇文章主要介紹了解決SpringMvc后臺接收json數(shù)據(jù)中文亂碼問題的幾種方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-01-01
淺談JDK8中的Duration Period和ChronoUnit
在JDK8中,引入了三個非常有用的時間相關(guān)的API:Duration,Period和ChronoUnit。他們都是用來對時間進行統(tǒng)計的,本文將會詳細講解一下這三個API的使用2021-06-06

