Spring Security加密和匹配及原理解析
一. 密碼加密簡介
1. 散列加密概述
我們開發(fā)時進行密碼加密,可用的加密手段有很多,比如對稱加密、非對稱加密、信息摘要等。在一般的項目里,常用的就是信息摘要算法,也可以被稱為散列加密函數(shù),或者稱為散列算法、哈希函數(shù)。這是一種可以從任何數(shù)據(jù)中創(chuàng)建數(shù)字“指紋”的方法,常用的散列函數(shù)有 MD5 消息摘要算法、安全散列算法(Secure Hash Algorithm)等。
2. 散列加密原理
散列函數(shù)通過把消息或數(shù)據(jù)壓縮成摘要信息,使得數(shù)據(jù)量變小,將數(shù)據(jù)的格式固定下來,然后將數(shù)據(jù)打亂混合,再重新創(chuàng)建成一個散列值,從而達到加密的目的。散列值通常用一個短的隨機字母和數(shù)字組成的字符串來代表,一個好的散列函數(shù)在輸入域中很少出現(xiàn)散列沖突。在散列表和數(shù)據(jù)處理時,如果我們不抑制沖突來區(qū)別數(shù)據(jù),會使得數(shù)據(jù)庫中的記錄很難找到。
但是僅僅使用散列函數(shù)還不夠,如果我們只是單純的使用散列函數(shù)而不做特殊處理,其實是有風(fēng)險的!比如在兩個用戶密碼明文相同時,生成的密文也會相同,這樣就增加了密碼泄漏的風(fēng)險。
所以為了增加密碼的安全性,一般在密碼加密過程中還需要“加鹽”,而所謂的“鹽”可以是一個隨機數(shù),也可以是用戶名。”加鹽“之后,即使密碼的明文相同,用戶生成的密碼密文也不相同,這就可以極大的提高密碼的安全性。
傳統(tǒng)的加鹽方式需要在數(shù)據(jù)庫中利用專門的字段來記錄鹽值,這個字段可以是用戶名字段(因為用戶名唯一),也可以是一個專門記錄鹽值的字段,但這樣的配置比較繁瑣。
二、SpringSecurity 中的密碼源碼分析
當(dāng)我們項目只引入springsecurity依賴之后,接下來什么事情都不用做,我們直接來啟動項目。
在項目啟動過程中,我們會看到如下一行日志:
Using generated security password: 10abfb2j-36e1-446a-jh9b-f70024fc89ab
這就是 Spring Security 為默認用戶 user 生成的臨時密碼,是一個 UUID 字符串。這個密碼和用戶相關(guān)的自動化配置類在 UserDetailsServiceAutoConfiguration
里邊,在該類的 getOrDeducePassword
方法中,我們看到如下一行日志:
if (user.isPasswordGenerated()) { logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword())); }
毫無疑問,我們在控制臺看到的日志就是從這里打印出來的。打印的條件是 isPasswordGenerated 方法返回 true,即密碼是默認生成的。
進而我們發(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;
可以看到,默認的用戶名就是 user,默認的密碼則是 UUID,而默認情況下,passwordGenerated 也為 true。
SecurityProperties默認的用戶就定義在它里邊,是一個靜態(tài)內(nèi)部類,我們?nèi)绻x自己的用戶名密碼,必然是要去覆蓋默認配置,我們先來看下 SecurityProperties 的定義:
@ConfigurationProperties(prefix = "spring.security") publicclass SecurityProperties {}
這就很清晰了,我們只需要以 spring.security.user 為前綴,去定義用戶名密碼即可:
spring.security.user.name=admin spring.security.user.password=123456
這就是我們新定義的用戶名密碼。
在 properties 中定義的用戶名密碼最終是通過 set 方法注入到屬性中去的,這里我們順便來看下 SecurityProperties.User#setPassword 方法:
public void setPassword(String password) { if (!StringUtils.hasLength(password)) { return; } this.passwordGenerated = false; this.password = password; }
從這里我們可以看到,application.properties 中定義的密碼在注入進來之后,還順便設(shè)置了 passwordGenerated 屬性為 false,這個屬性設(shè)置為 false 之后,控制臺就不會打印默認的密碼了。
此時重啟項目,就可以使用自己定義的用戶名/密碼登錄了
除了上面的配置文件這種方式之外,我們也可以在配置類中配置用戶名/密碼。
@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 了,這是一個非常關(guān)鍵的東西
三、PasswordEncoder
1、PasswordEncoder
security中用于加密的接口就是PasswordEncoder,接口用于執(zhí)行密碼的單向轉(zhuǎn)換,以便安全地存儲密碼,源碼如下
public interface PasswordEncoder { //該方法提供了明文密碼的加密處理,加密后密文的格式主要取決于PasswordEncoder接口實現(xiàn)類實例。 String encode(CharSequence rawPassword); //匹配存儲的密碼以及登錄時傳遞的密碼(登錄密碼是經(jīng)過加密處理后的字符串)是否匹配,如果匹配該方法則會返回true,第一個參數(shù)表示需要被解析的密碼 第二個參數(shù)表示存儲的密碼 boolean matches(CharSequence rawPassword, String encodedPassword); default boolean upgradeEncoding(String encodedPassword) { return false; } }
PasswordEncoder 中的 encode 方法是我們在用戶注冊的時候手動調(diào)用,而matches 方法,則是由系統(tǒng)調(diào)用,默認是在 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")); } }
可以看到,密碼比對就是通過 passwordEncoder.matches 方法來進行的。
Spring Security 提供了多種密碼加密方案,官方推薦使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 強哈希函數(shù),開發(fā)者在使用時可以選擇提供 strength 和 SecureRandom 實例。strength 越大,密鑰的迭代次數(shù)越多,密鑰迭代次數(shù)為 2^strength。strength 取值在 4~31 之間,默認為 10。
不同于 Shiro 中需要自己處理密碼加鹽,在 Spring Security 中,BCryptPasswordEncoder 就自帶了鹽,處理起來非常方便。而 BCryptPasswordEncoder 就是 PasswordEncoder 接口的實現(xiàn)類。其他實現(xiàn)類列表如下
舉例使用
三、hutool 工具 BCrypt 進行加密和匹配
( 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("重新進行加密后密碼:"+n.encode(pas)); if (n.matches(pas,pas1)){ System.out.println("True - 匹配:"+"11111111111111111111111"); }else { System.out.println("False - 未匹配:"+"2222222222222222222222"); } System.out.println("======================= 兩種方法類似都可進行加密和匹配 ======================="); 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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
RestTemplate報錯I/O?error?on?POST?request?for的解決辦法
這篇文章主要給大家介紹了關(guān)于RestTemplate報錯I/O?error?on?POST?request?for的解決辦法,文中通過代碼實例將解決的辦法介紹的非常詳細,需要的朋友可以參考下2023-08-08java實現(xiàn)批量導(dǎo)入Excel表格數(shù)據(jù)到數(shù)據(jù)庫
這篇文章主要為大家詳細介紹了java實現(xiàn)批量導(dǎo)入Excel表格數(shù)據(jù)到數(shù)據(jù)庫,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-08-08java連接HBase,連接不上報錯can not resolve問題及解決
這篇文章主要介紹了java連接HBase,連接不上報錯can not resolve問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06Spring Cloud Gateway替代zuul作為API網(wǎng)關(guān)的方法
本文簡要介紹如何使用Spring Cloud Gateway 作為API 網(wǎng)關(guān)(不是使用zuul作為網(wǎng)關(guān)),結(jié)合實例代碼給大家詳細講解,感興趣的朋友跟隨小編一起看看吧2023-02-02Java 高并發(fā)九:鎖的優(yōu)化和注意事項詳解
本文主要介紹Java高并發(fā)鎖的優(yōu)化和注意事項,這里整理了詳細的資料,并講解了 1. 鎖優(yōu)化的思路和方法 2. 虛擬機內(nèi)的鎖優(yōu)化 3. 一個錯誤使用鎖的案例 4. ThreadLocal及其源碼分析等知識,有需要的小伙伴可以參考下2016-09-09