Spring?Security如何實(shí)現(xiàn)升級(jí)密碼加密方式詳解
本章內(nèi)容
- 密碼加密方式怎么升級(jí)?
- spring security底層怎么實(shí)現(xiàn)的密碼加密方式升級(jí)?
密碼加密方式怎么升級(jí)?
前面我們學(xué)過DelegatingPasswordEncoder
類,但是不清楚他到底是做什么的,我也沒講的很清楚。所以呢,我們就重新再講一講它的另一個(gè)實(shí)際應(yīng)用。
小明呢,有一天在刷新聞。突然收到了一篇關(guān)于MD5
加密存在重大漏洞的報(bào)告, 而最佳的代替加密方案是BCrypt
。此時(shí)小明慌了。
因?yàn)樗?xiàng)目里面就是用著MD5
加密。那現(xiàn)在怎么辦呢?小明的用戶體量比較大,你不可能叫客戶/程序員一個(gè)個(gè)去改是吧?
spring security就提供了一種這種情況的解決方案。
在用戶登錄你的賬戶時(shí),自動(dòng)的升級(jí)您的密碼加密方式。比如說從MD5
加密方式變成BCrypt
但是呢,這種方式有一個(gè)前提。您數(shù)據(jù)庫的用戶密碼必須要有ID
,也就是花括號(hào)的那一部分{noop}123456
。
當(dāng)然如果花括號(hào)沒有,然后數(shù)據(jù)體量就比較大,你只能重寫DelegatingPasswordEncoder
。
抄代碼的地方就在PasswordEncoderFactories#createDelegatingPasswordEncoder
, 也就是你數(shù)據(jù)庫中的密碼沒有花括號(hào)部分(拿不到ID)的情況下, 使用BCryptPasswordEncoder
小白: "那在spring security中哪一部分定義了這項(xiàng)功能?"
升級(jí)方案源碼
首先我們得思考。什么情況下才會(huì)進(jìn)行密碼升級(jí)?
按照常理來說,應(yīng)該是在用戶登錄成功之后進(jìn)行密碼升級(jí)。所以我們在找源碼的時(shí)候,應(yīng)該先去找認(rèn)證成功的那部分源碼,絕對能找到這部分功能。
我第一反應(yīng)找UsernamePasswordAuthenticationFilter
的AbstractAuthenticationProcessingFilter
抽象類的doFilter
方法
但是不幸的是這里找不到我們想要的功能。所以我立即反應(yīng)起來這項(xiàng)功能應(yīng)該是在認(rèn)證器這邊。
DaoAuthenticationProvider
和AbstractUserDetailsAuthenticationProvider
找到的認(rèn)證成功之后,他執(zhí)行的一段函數(shù)??梢悦黠@的看出有更新密碼的過程。
這里只要保證upgradeEncoding == true
,那么就可以進(jìn)入更新密碼的過程。
這里我們看到了一段代碼this.userDetailsPasswordService
, 可以百分百確定,我們的功能就在這個(gè)接口里面。
至于if的另一個(gè)函數(shù)upgradeEncoding
, 你只要知道用戶輸入密碼和數(shù)據(jù)庫密碼ID不同就為 true, 相同就為 false, 當(dāng)然還有ID相同不同長度的解決方案, 這里就不細(xì)談了
public interface UserDetailsPasswordService { UserDetails updatePassword(UserDetails user, String newPassword); }
如果你英文能力比較強(qiáng)的話,可以直接去查看這個(gè)接口上面就會(huì)有注釋,內(nèi)容就是修改用戶名的密碼就這么簡單。
既然已經(jīng)知道這個(gè)接口的存在了,那現(xiàn)在的問題是怎么讓spring security調(diào)用我們所實(shí)現(xiàn)的這個(gè)接口呢?
我現(xiàn)在羅列出三張圖片。就可以從這三張圖片中總結(jié)出三種加載我們實(shí)現(xiàn)類的方法。
實(shí)戰(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)在就會(huì)有人問的。我并沒有寫出從MD5
加密方式升級(jí)到BCrypt
加密方式。他是怎么自動(dòng)升級(jí)到BCrypt
加密方式的?
帶著問題看源碼
他是怎么自動(dòng)升級(jí)到BCrypt
加密方式的?
我們知道spring security里面默認(rèn)使用的PasswordEncoder
是這樣的。
@Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); }
不知道當(dāng)做知道哈
他們內(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)在進(jì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
在這個(gè)類中被叫做idForEncode
了解了這個(gè)之后,再關(guān)注這幾行代碼。
this.idForEncode = idForEncode; this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
是不是相當(dāng)于
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)在你對比一下這個(gè)函數(shù)跟前面構(gòu)造函數(shù)的名字看看。
構(gòu)造函數(shù)的變量叫 idForEncode
, encode
函數(shù)也叫 idForEncode
, 前面的構(gòu)造函數(shù),我們發(fā)現(xiàn)這個(gè)變量其實(shí)已經(jīng)被保存在DelegatingPasswordEncoder
類里面了。而且值還是"bcrypt"
而構(gòu)造函數(shù)里面this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode)
idToPasswordEncoder
就是個(gè)Map, k是每種加密對象的id, v是每種加密算法
比如: key = "bcrypt"
, 那么 value = "BCryptPasswordEncoder"
所以 idToPasswordEncoder
在 encode
函數(shù)時(shí), 是BCryptPasswordEncoder
類
小白: "什么玩意兒, 亂七八糟的, 看不懂"
小黑: "抱歉表達(dá)能力不行, 我簡單點(diǎn)說"
小黑: "因?yàn)?code>PasswordEncoderFactories.createDelegatingPasswordEncoder()函數(shù)使用bcrypt
作為默認(rèn)加密方式, 所以在調(diào)用PasswordEncoder.encode
時(shí)默認(rèn)也使用bcrypt
"
小黑: "還不懂就配合下面的圖片看看"
造成它默認(rèn)是BCryptPasswordEncoder
的原因是什么?
就上面這一行代碼
搞懂這個(gè)有什么作用呢?
Spring security默認(rèn)全部加密方式升級(jí)方案全部都是bcrypt
,那如果我們要自定義升級(jí)到我們需要的加密方式呢?
重寫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; /** * 升級(jí)用戶密碼為當(dāng)前加密方式 * * @param user 要修改的用戶, 這個(gè)用戶必須有 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(/* 你的認(rèn)證七 */) .userDetailsService(/* 加載用戶方式 */) .passwordEncoder(/* 密碼加密方式 */) .userDetailsPasswordManager(/* 第三種更新加密的方式 */); return authenticationManagerBuilder.build(); }
這種方式比較麻煩, 只有你需要重寫某個(gè)Provider
的時(shí)候才會(huì)用到
一般我們使用第二種方式就行
以上就是Spring Security如何實(shí)現(xiàn)升級(jí)密碼加密方式詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring Security升級(jí)密碼加密的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring?MVC基于注解的使用之JSON數(shù)據(jù)處理的方法
這篇文章主要介紹了Spring?MVC基于注解的使用JSON數(shù)據(jù)處理,json是一種輕量級(jí)的數(shù)據(jù)交換格式,是一種理想的數(shù)據(jù)交互語言,它易于閱讀和編寫,同時(shí)也易于機(jī)器解析和生成,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05SpringBoot整合MP通過Redis實(shí)現(xiàn)二級(jí)緩存方式
這篇文章主要介紹了SpringBoot整合MP通過Redis實(shí)現(xiàn)二級(jí)緩存方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01java的各種集合為什么不安全(List、Set、Map)以及代替方案
這篇文章主要介紹了java的各種集合為什么不安全(List、Set、Map)以及代替方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10在SpringBoot中更改默認(rèn)端口的方法總結(jié)
在本文中,小編將帶大家學(xué)習(xí)如何在 Spring Boot 中更改默認(rèn)端口,默認(rèn)情況下,嵌入式 Web 服務(wù)器使用 8080端口來啟動(dòng) Spring 引導(dǎo)應(yīng)用程序,有幾種方法可以更改該端口,文中介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07java中如何判斷數(shù)組中是否包含某個(gè)元素的幾種方法
相信大家在操作Java的時(shí)候,經(jīng)常會(huì)要檢查一個(gè)數(shù)組(無序)是否包含一個(gè)特定的值,這篇文章主要給大家介紹了關(guān)于java中如何判斷數(shù)組中是否包含某個(gè)元素的幾種方法,需要的朋友可以參考下2024-08-08Java結(jié)構(gòu)性設(shè)計(jì)模式中的裝飾器模式介紹使用
裝飾器模式又名包裝(Wrapper)模式。裝飾器模式以對客戶端透明的方式拓展對象的功能,是繼承關(guān)系的一種替代方案,本篇文章以虹貓藍(lán)兔生動(dòng)形象的為你帶來詳細(xì)講解2022-09-09IDEA的默認(rèn)快捷鍵設(shè)置與Eclipse的常用快捷鍵的設(shè)置方法
這篇文章主要介紹了IDEA的默認(rèn)快捷鍵設(shè)置與Eclipse的常用快捷鍵的設(shè)置方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01