Spring Security加密和匹配及原理解析
一. 密碼加密簡(jiǎn)介
1. 散列加密概述
我們開發(fā)時(shí)進(jìn)行密碼加密,可用的加密手段有很多,比如對(duì)稱加密、非對(duì)稱加密、信息摘要等。在一般的項(xiàng)目里,常用的就是信息摘要算法,也可以被稱為散列加密函數(shù),或者稱為散列算法、哈希函數(shù)。這是一種可以從任何數(shù)據(jù)中創(chuàng)建數(shù)字“指紋”的方法,常用的散列函數(shù)有 MD5 消息摘要算法、安全散列算法(Secure Hash Algorithm)等。
2. 散列加密原理
散列函數(shù)通過(guò)把消息或數(shù)據(jù)壓縮成摘要信息,使得數(shù)據(jù)量變小,將數(shù)據(jù)的格式固定下來(lái),然后將數(shù)據(jù)打亂混合,再重新創(chuàng)建成一個(gè)散列值,從而達(dá)到加密的目的。散列值通常用一個(gè)短的隨機(jī)字母和數(shù)字組成的字符串來(lái)代表,一個(gè)好的散列函數(shù)在輸入域中很少出現(xiàn)散列沖突。在散列表和數(shù)據(jù)處理時(shí),如果我們不抑制沖突來(lái)區(qū)別數(shù)據(jù),會(huì)使得數(shù)據(jù)庫(kù)中的記錄很難找到。
但是僅僅使用散列函數(shù)還不夠,如果我們只是單純的使用散列函數(shù)而不做特殊處理,其實(shí)是有風(fēng)險(xiǎn)的!比如在兩個(gè)用戶密碼明文相同時(shí),生成的密文也會(huì)相同,這樣就增加了密碼泄漏的風(fēng)險(xiǎn)。
所以為了增加密碼的安全性,一般在密碼加密過(guò)程中還需要“加鹽”,而所謂的“鹽”可以是一個(gè)隨機(jī)數(shù),也可以是用戶名。”加鹽“之后,即使密碼的明文相同,用戶生成的密碼密文也不相同,這就可以極大的提高密碼的安全性。
傳統(tǒng)的加鹽方式需要在數(shù)據(jù)庫(kù)中利用專門的字段來(lái)記錄鹽值,這個(gè)字段可以是用戶名字段(因?yàn)橛脩裘ㄒ?,也可以是一個(gè)專門記錄鹽值的字段,但這樣的配置比較繁瑣。
二、SpringSecurity 中的密碼源碼分析
當(dāng)我們項(xiàng)目只引入springsecurity依賴之后,接下來(lái)什么事情都不用做,我們直接來(lái)啟動(dòng)項(xiàng)目。
在項(xiàng)目啟動(dòng)過(guò)程中,我們會(huì)看到如下一行日志:
Using generated security password: 10abfb2j-36e1-446a-jh9b-f70024fc89ab
這就是 Spring Security 為默認(rèn)用戶 user 生成的臨時(shí)密碼,是一個(gè) UUID 字符串。這個(gè)密碼和用戶相關(guān)的自動(dòng)化配置類在 UserDetailsServiceAutoConfiguration
里邊,在該類的 getOrDeducePassword
方法中,我們看到如下一行日志:
if (user.isPasswordGenerated()) { logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword())); }
毫無(wú)疑問(wèn),我們?cè)诳刂婆_(tái)看到的日志就是從這里打印出來(lái)的。打印的條件是 isPasswordGenerated 方法返回 true,即密碼是默認(rèn)生成的。
進(jìn)而我們發(fā)現(xiàn),user.getPassword 出現(xiàn)在 SecurityProperties 中,在 SecurityProperties 中我們看到如下定義:
/** * Default user name. */ private String name = "user"; /** * Password for the default user name. */ private String password = UUID.randomUUID().toString(); private boolean passwordGenerated = true;
可以看到,默認(rèn)的用戶名就是 user,默認(rèn)的密碼則是 UUID,而默認(rèn)情況下,passwordGenerated 也為 true。
SecurityProperties默認(rèn)的用戶就定義在它里邊,是一個(gè)靜態(tài)內(nèi)部類,我們?nèi)绻x自己的用戶名密碼,必然是要去覆蓋默認(rèn)配置,我們先來(lái)看下 SecurityProperties 的定義:
@ConfigurationProperties(prefix = "spring.security") publicclass SecurityProperties {}
這就很清晰了,我們只需要以 spring.security.user 為前綴,去定義用戶名密碼即可:
spring.security.user.name=admin spring.security.user.password=123456
這就是我們新定義的用戶名密碼。
在 properties 中定義的用戶名密碼最終是通過(guò) set 方法注入到屬性中去的,這里我們順便來(lái)看下 SecurityProperties.User#setPassword 方法:
public void setPassword(String password) { if (!StringUtils.hasLength(password)) { return; } this.passwordGenerated = false; this.password = password; }
從這里我們可以看到,application.properties 中定義的密碼在注入進(jìn)來(lái)之后,還順便設(shè)置了 passwordGenerated 屬性為 false,這個(gè)屬性設(shè)置為 false 之后,控制臺(tái)就不會(huì)打印默認(rèn)的密碼了。
此時(shí)重啟項(xiàng)目,就可以使用自己定義的用戶名/密碼登錄了
除了上面的配置文件這種方式之外,我們也可以在配置類中配置用戶名/密碼。
@Configuration publicclass SecurityConfig extends WebSecurityConfigurerAdapter { @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin") .password("123456").roles("admin"); } }
在配置類中配置,我們就要指定 PasswordEncoder 了,這是一個(gè)非常關(guān)鍵的東西
三、PasswordEncoder
1、PasswordEncoder
security中用于加密的接口就是PasswordEncoder,接口用于執(zhí)行密碼的單向轉(zhuǎn)換,以便安全地存儲(chǔ)密碼,源碼如下
public interface PasswordEncoder { //該方法提供了明文密碼的加密處理,加密后密文的格式主要取決于PasswordEncoder接口實(shí)現(xiàn)類實(shí)例。 String encode(CharSequence rawPassword); //匹配存儲(chǔ)的密碼以及登錄時(shí)傳遞的密碼(登錄密碼是經(jīng)過(guò)加密處理后的字符串)是否匹配,如果匹配該方法則會(huì)返回true,第一個(gè)參數(shù)表示需要被解析的密碼 第二個(gè)參數(shù)表示存儲(chǔ)的密碼 boolean matches(CharSequence rawPassword, String encodedPassword); default boolean upgradeEncoding(String encodedPassword) { return false; } }
PasswordEncoder 中的 encode 方法是我們?cè)谟脩糇?cè)的時(shí)候手動(dòng)調(diào)用,而matches 方法,則是由系統(tǒng)調(diào)用,默認(rèn)是在 DaoAuthenticationProvider#additionalAuthenticationChecks 方法中調(diào)用的。
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } String presentedPassword = authentication.getCredentials().toString(); if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } }
可以看到,密碼比對(duì)就是通過(guò) passwordEncoder.matches 方法來(lái)進(jìn)行的。
Spring Security 提供了多種密碼加密方案,官方推薦使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 強(qiáng)哈希函數(shù),開發(fā)者在使用時(shí)可以選擇提供 strength 和 SecureRandom 實(shí)例。strength 越大,密鑰的迭代次數(shù)越多,密鑰迭代次數(shù)為 2^strength。strength 取值在 4~31 之間,默認(rèn)為 10。
不同于 Shiro 中需要自己處理密碼加鹽,在 Spring Security 中,BCryptPasswordEncoder 就自帶了鹽,處理起來(lái)非常方便。而 BCryptPasswordEncoder 就是 PasswordEncoder 接口的實(shí)現(xiàn)類。其他實(shí)現(xiàn)類列表如下
舉例使用
三、hutool 工具 BCrypt 進(jìn)行加密和匹配
( cn.hutool.crypto.digest.BCrypt )
import cn.hutool.crypto.digest.BCrypt; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class UmsMemberController { public static void main(String[] args) { String pas="123456"; String pas1="$2a$10$mMslOUUCblGnotpq5G3j2er8OuqsIU08YF.x50//YOB6vLrGNd7Wq"; BCryptPasswordEncoder n=new BCryptPasswordEncoder(); System.out.println("加密前密碼:"+pas); System.out.println("加密后密碼:"+pas1); System.out.println("重新進(jìn)行加密后密碼:"+n.encode(pas)); if (n.matches(pas,pas1)){ System.out.println("True - 匹配:"+"11111111111111111111111"); }else { System.out.println("False - 未匹配:"+"2222222222222222222222"); } System.out.println("======================= 兩種方法類似都可進(jìn)行加密和匹配 ======================="); if (BCrypt.checkpw(pas,pas1)){ System.out.println("True - 匹配:"+"11111111111111111111111"); }else { System.out.println("False - 未匹配:"+"2222222222222222222222"); } } }
到此這篇關(guān)于Spring Security加密和匹配的文章就介紹到這了,更多相關(guān)Spring Security加密和匹配內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 一文掌握SpringSecurity?BCrypt密碼加密和解密
- Spring?Security如何實(shí)現(xiàn)升級(jí)密碼加密方式詳解
- Spring Security基于散列加密方案實(shí)現(xiàn)自動(dòng)登錄功能
- Spring security密碼加密實(shí)現(xiàn)代碼實(shí)例
- Spring security實(shí)現(xiàn)對(duì)賬戶進(jìn)行加密
- Spring Security使用數(shù)據(jù)庫(kù)認(rèn)證及用戶密碼加密和解密功能
- spring security 5.x實(shí)現(xiàn)兼容多種密碼的加密方式
相關(guān)文章
j2ee之AJAX二級(jí)聯(lián)動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了j2ee之AJAX二級(jí)聯(lián)動(dòng)效果的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08RestTemplate報(bào)錯(cuò)I/O?error?on?POST?request?for的解決辦法
這篇文章主要給大家介紹了關(guān)于RestTemplate報(bào)錯(cuò)I/O?error?on?POST?request?for的解決辦法,文中通過(guò)代碼實(shí)例將解決的辦法介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08java實(shí)現(xiàn)批量導(dǎo)入Excel表格數(shù)據(jù)到數(shù)據(jù)庫(kù)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)批量導(dǎo)入Excel表格數(shù)據(jù)到數(shù)據(jù)庫(kù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08java連接HBase,連接不上報(bào)錯(cuò)can not resolve問(wèn)題及解決
這篇文章主要介紹了java連接HBase,連接不上報(bào)錯(cuò)can not resolve問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06Java必會(huì)的Synchronized底層原理剖析
synchronized作為Java程序員最常用同步工具,很多人卻對(duì)它的用法和實(shí)現(xiàn)原理一知半解,以至于還有不少人認(rèn)為synchronized是重量級(jí)鎖,性能較差,盡量少用。但不可否認(rèn)的是synchronized依然是并發(fā)首選工具,本文就來(lái)詳細(xì)講講2022-10-10Spring Cloud Gateway替代zuul作為API網(wǎng)關(guān)的方法
本文簡(jiǎn)要介紹如何使用Spring Cloud Gateway 作為API 網(wǎng)關(guān)(不是使用zuul作為網(wǎng)關(guān)),結(jié)合實(shí)例代碼給大家詳細(xì)講解,感興趣的朋友跟隨小編一起看看吧2023-02-02Java 高并發(fā)九:鎖的優(yōu)化和注意事項(xiàng)詳解
本文主要介紹Java高并發(fā)鎖的優(yōu)化和注意事項(xiàng),這里整理了詳細(xì)的資料,并講解了 1. 鎖優(yōu)化的思路和方法 2. 虛擬機(jī)內(nèi)的鎖優(yōu)化 3. 一個(gè)錯(cuò)誤使用鎖的案例 4. ThreadLocal及其源碼分析等知識(shí),有需要的小伙伴可以參考下2016-09-09