SpringSecurity中PasswordEncoder的使用
概述
任何一個登錄系統(tǒng)的密碼不能明文存儲,萬一發(fā)生數(shù)據(jù)庫泄漏事故(不管是內(nèi)部人員導(dǎo)出數(shù)據(jù)庫數(shù)據(jù)還是被黑客攻擊破解數(shù)據(jù)庫實例節(jié)點拿到數(shù)據(jù)庫數(shù)據(jù)等,又或者是其他情況造成的),將產(chǎn)生巨大的損失。因此明文密碼在存儲到數(shù)據(jù)庫之前需要加密處理。
加密算法有很多,大致有如下分類:
- 哈希函數(shù)算法:包括消息摘要算法(MD4,MD5等),消息摘要算法是一種特殊類型的哈希函數(shù)算法,用于將任意長度的數(shù)據(jù)映射為固定長度的哈希值或摘要。摘要值通常用于驗證數(shù)據(jù)的完整性、數(shù)字簽名、身份驗證等用途。算法包括:
- MD5(Message Digest Algorithm 5):已經(jīng)不推薦使用,存在碰撞攻擊漏洞
- SHA-1(Secure Hash Algorithm 1):也存在碰撞攻擊漏洞,逐漸被淘汰
- SHA-256、SHA-384、SHA-512:SHA-2系列,目前被廣泛應(yīng)用,提供更高的安全性
- 對稱加密算法使用相同的密鑰進(jìn)行加密和解密。常見的對稱加密算法包括:
- DES(Data Encryption Standard):已經(jīng)不推薦使用,因為密鑰長度較短易受到攻擊
- 3DES(Triple DES):DES增強(qiáng)版,使用三個密鑰提高安全性
- AES(Advanced Encryption Standard):目前廣泛應(yīng)用的對稱加密算法,具有較高的安全性和性能
- 非對稱加密算法(公鑰加密算法):
非對稱加密算法使用一對密鑰,分別是公鑰和私鑰,公鑰用于加密,私鑰用于解密。常見的非對稱加密算法包括: - RSA(Rivest-Shamir-Adleman):基于大數(shù)分解難題,被廣泛用于數(shù)字簽名和密鑰交換
- ECC(Elliptic Curve Cryptography):利用橢圓曲線上的離散對數(shù)問題,相比RSA,提供相同安全級別下更短的密鑰長度和更高的性能
反查表、彩虹表
上文提到一些已經(jīng)不推薦使用、逐漸被淘汰的算法,如MD5、SHA-1。因為不管是MD5還是SHA-1算法,對于給定的某個字符串(密碼),經(jīng)過哈希函數(shù)計算之后得到的結(jié)果都是固定的。比如admin
經(jīng)過MD5計算(有16位和32位之分,這里用的是16位)結(jié)果始終是7a57a5a743894a0e
,root
經(jīng)過SHA-1計算后結(jié)果始終是dc76e9f0c0006e8f919e0c515c66dbba3982f785
。
那黑客們就可以維護(hù)一個數(shù)據(jù)庫,其字段包括加密后的密文、加密算法、明文密碼,意味著可以根據(jù)密文反查明文密碼。這就是反查表。
基于反查表,黑客們后來發(fā)明更高級的彩虹表。
在Java Web開發(fā)中,我們會遇到各種各樣的安全問題。作為最基本的,數(shù)據(jù)庫密碼的安全性如何得到保證呢?此時Spring Security隆重登場,可以幫助我們解決這個問題。
Spring Security
實例
加密密碼的配置類(代碼片段):
import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity @EnableMethodSecurity() public class WebSecurityConfig implements SecurityFilterChain { @Resource private UserDetailsService userDetailsService; @Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder .userDetailsService(this.userDetailsService) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
PasswordEncoder
PasswordEncoder接口定義如下:
public interface PasswordEncoder { // 用來對明文密碼進(jìn)行加密 String encode(CharSequence rawPassword); // 用來進(jìn)行密碼比對 boolean matches(CharSequence rawPassword, String encodedPassword); // 用來判斷當(dāng)前密碼是否需要升級,默認(rèn)返回false表示不需要升級 default boolean upgradeEncoding(String encodedPassword) { return false; } }
尚未廢棄的實現(xiàn)類,都是自適應(yīng)單向函數(shù)(Adaptive One-way Functions)來處理密碼問題,這種函數(shù)在進(jìn)行密碼匹配時,會有意占用大量系統(tǒng)資源(例如CPU、內(nèi)存等),可以增加惡意用戶攻擊系統(tǒng)的難度。包括:bcrypt、PBKDF2、scrypt以及argon2。
因此實現(xiàn)類包括:
- BCryptPasswordEncoder:使用bcrypt強(qiáng)散列算法對密碼進(jìn)行加密,為提高密碼的安全性,bcrypt算法故意降低運(yùn)行速度,以增強(qiáng)密碼破解的難度。BCryptPasswordEncoder自帶salt加鹽機(jī)制,即使相同的明文每次生成的加密字符串都不相同。默認(rèn)強(qiáng)度為10(參考源碼里的strength字段),開發(fā)者可以根據(jù)自己的服務(wù)器性能進(jìn)行調(diào)整,以確保密碼驗證時間約為1秒鐘(官方建議密碼驗證時間為1秒鐘,既可以提高系統(tǒng)安全性,又不會過多影響系統(tǒng)運(yùn)行性能)
- Argon2PasswordEncoder:使用Argon2算法對密碼進(jìn)行加密,Argon2曾在Password Hashing Competition競賽中獲勝。為了解決在定制硬件上密碼容易被破解的問題,Argon2也是故意降低運(yùn)算速度,同時需要大量內(nèi)存
- Pbkdf2PasswordEncoder:使用PBKDF2算法對密碼進(jìn)行加密,可用于FIPS(Federal Information Processing Standard,美國聯(lián)邦信息處理標(biāo)準(zhǔn))認(rèn)證
- SCryptPasswordEncoder:使用scrypt算法對密碼進(jìn)行加密
幾個已經(jīng)被廢棄的基于消息摘要算法的實現(xiàn)類:
- NoOpPasswordEncoder:密碼明文存儲,不可用于生產(chǎn)環(huán)境
- Md4PasswordEncoder:使用Md4算法加密密碼
- LdapShaPasswordEncoder:使用SHA算法
- StandardPasswordEncoder:使用SHA-256算法
- MessageDigestPasswordEncoder:使用MD5算法
BCryptPasswordEncoder
public String encode(CharSequence rawPassword) { if (rawPassword == null) { throw new IllegalArgumentException("rawPassword cannot be null"); } String salt = getSalt(); return BCrypt.hashpw(rawPassword.toString(), salt); } private String getSalt() { if (this.random != null) { return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random); } return BCrypt.gensalt(this.version.getVersion(), this.strength); }
使用Spring Security提供的BCrypt工具類生成鹽(salt);然后,根據(jù)鹽和明文密碼生成最終的密文。所謂加鹽,就是在初始化明文數(shù)據(jù)時,由系統(tǒng)自動向該明文里添加一些附加數(shù)據(jù),然后散列。引入加鹽機(jī)制的目的是進(jìn)一步提高加密數(shù)據(jù)的安全性,單向散列加密及加鹽思想廣泛應(yīng)用于系統(tǒng)登錄過程中的密碼生成和校驗。
構(gòu)造方法:
public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom random) { if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) { throw new IllegalArgumentException("Bad strength"); } this.version = version; this.strength = (strength == -1) ? 10 : strength; this.random = random; }
從構(gòu)造函數(shù)可知,strength長度默認(rèn)為10,最小值為BCrypt.MIN_LOG_ROUNDS=4
,最大值為BCrypt.MAX_LOG_ROUNDS=31
。顯而易見,長度越長,加密算法越復(fù)雜,被惡意破解攻擊的難度越大,但是也會增加系統(tǒng)負(fù)載,增加加密計算時長和存儲空間。因此需要取得權(quán)衡,默認(rèn)情況下使用Spring Security建議的長度10即可。
PasswordEncoderFactories
瀏覽一下spring-security-crypto-6.2.3
源碼結(jié)構(gòu):
不難發(fā)現(xiàn)PasswordEncoderFactories這個類,采用工廠方法模式,源碼:
public static PasswordEncoder createDelegatingPasswordEncoder() { String encodingId = "bcrypt"; Map<String, PasswordEncoder> encoders = new HashMap(); encoders.put(encodingId, new BCryptPasswordEncoder()); encoders.put("ldap", new LdapShaPasswordEncoder()); encoders.put("MD4", new Md4PasswordEncoder()); encoders.put("MD5", new MessageDigestPasswordEncoder("MD5")); encoders.put("noop", NoOpPasswordEncoder.getInstance()); encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5()); encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()); encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1()); encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()); encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1")); encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256")); encoders.put("sha256", new StandardPasswordEncoder()); encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2()); encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()); return new DelegatingPasswordEncoder(encodingId, encoders); }
靜態(tài)方法createDelegatingPasswordEncoder,encoders中存儲每一種密碼加密方案的id和所對應(yīng)的加密類,如bcrypt對應(yīng)BcryptPassword。最后,返回代理類DelegatingPasswordEncoder實例,并且默認(rèn)使用的加密方案是BCryptPasswordEncoder。
DelegatingPasswordEncoder
DelegatingPasswordEncoder,采用代理模式,Spring Security 5.0版本后默認(rèn)的密碼加密方案,主要考慮如下三方面的因素:
- 兼容性:使用DelegatingPasswordEncoder可以幫助許多使用舊密碼加密方式的系統(tǒng)順利遷移到Spring Security中,它允許在同一個系統(tǒng)中同時存在多種不同的密碼加密方案
- 便捷性:密碼存儲的最佳方案不可能一直不變,使用DelegatingPasswordEncoder作為默認(rèn)的密碼加密方案,當(dāng)需要修改加密方案時,只需要修改很小一部分代碼即可實現(xiàn)
- 穩(wěn)定性:作為一個框架,Spring Security不能經(jīng)常進(jìn)行重大更改,使用Delegating PasswordEncoder可以方便地對密碼進(jìn)行升級(自動從一個加密方案升級到另外一個加密方案)
屬性如下:
// 默認(rèn)的前綴和后綴,用于包裹將來生成的加密方案的id private static final String DEFAULT_ID_PREFIX = "{"; private static final String DEFAULT_ID_SUFFIX = "}"; // 構(gòu)造方法里支持傳入用戶自定義的前綴和后綴 private final String idPrefix; private final String idSuffix; // 默認(rèn)的加密方案id private final String idForEncode; // 根據(jù)idForEncode從idToPasswordEncoder map中提取出來的 private final PasswordEncoder passwordEncoderForEncode; // 保存id和加密方案之間的映射 private final Map<String, PasswordEncoder> idToPasswordEncoder; // 默認(rèn)的密碼比對器,當(dāng)根據(jù)密碼加密方案的id無法找到對應(yīng)的加密方案時,就會使用默認(rèn)的密碼比對器。默認(rèn)類型是UnmappedIdPasswordEncoder private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
UnmappedIdPasswordEncoder是一個內(nèi)部私有類:
private class UnmappedIdPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence rawPassword) { // 直接拋出異常 throw new UnsupportedOperationException("encode is not supported"); } @Override public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) { // 并不會做任何密碼比對操作,直接拋出異常 String id = extractId(prefixEncodedPassword); throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\""); } }
核心方法encode
:
public String encode(CharSequence rawPassword) { return this.idPrefix + this.idForEncode + this.idSuffix + this.passwordEncoderForEncode.encode(rawPassword); }
作為一個代理類,不負(fù)責(zé)具體的加密工作,由加密類來完成,最后加上類似于{bcrypt}
這樣的前綴,不同的前綴表示使用不同的加密算法,即不同的PasswordEncoder實現(xiàn)類,當(dāng)然也包括自定義的加密類。
核心方法matches
:
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) { if (rawPassword == null && prefixEncodedPassword == null) { return true; } String id = extractId(prefixEncodedPassword); PasswordEncoder delegate = this.idToPasswordEncoder.get(id); if (delegate == null) { return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword); } String encodedPassword = extractEncodedPassword(prefixEncodedPassword); return delegate.matches(rawPassword, encodedPassword); }
extractId方法用于從加密字符串中提取出具體的加密方案id,也就是前綴和后綴包裹的字符串,如bcrypt,此方法就不貼出來了。根據(jù)加密方案id從map集合查找對應(yīng)的加密算法實現(xiàn)類,查找失敗則使用默認(rèn)的加密類,即UnmappedIdPasswordEncoder,然后就會拋出異常。
核心方法upgradeEncoding
:
public boolean upgradeEncoding(String prefixEncodedPassword) { String id = extractId(prefixEncodedPassword); if (!this.idForEncode.equalsIgnoreCase(id)) { return true; } else { String encodedPassword = extractEncodedPassword(prefixEncodedPassword); return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword); } }
如果當(dāng)前加密字符串所采用的加密方案不是默認(rèn)的BcryptPasswordEncoder ,就會自動進(jìn)行密碼升級,否則就調(diào)用默認(rèn)加密方案的upgradeEncoding方法判斷密碼是否需要升級。
自定義加密方案
業(yè)務(wù)開發(fā)中,如果Spring Security自帶的幾個加密類都不能滿足需求,或者業(yè)務(wù)場景比較復(fù)雜,需要兼容數(shù)據(jù)庫歷史未加密字段或加密算法不夠好的字段,則可能需要自定義加密類。
具體來說,實現(xiàn)PasswordEncoder接口類,并重寫3個方法。比如自定義一個使用SHA-512加密算法的加密類:
public class Sha512PasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence rawPassword) { return hashWithSha512(rawPassword.toString()); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { String hashedPassword = encode(rawPassword); return encodedPassword.equals(hashedPassword); } @Override public boolean upgradeEncoding(String prefixEncodedPassword) { // 不需要升級 return false; } private String hashWithSha512(String input) { StringBuilder result = new StringBuilder(); try { MessageDigest md = MessageDigest.getInstance("SHA-512"); byte [] digested = md.digest(input.getBytes()); for (int i = 0; i < digested.length; i++) { result.append(Integer.toHexString(0xFF & digested[i])); } } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Bad algorithm"); } return result.toString(); } }
最后需要配置一下使用此自定義類,使其生效。
參考
- 深入淺出Spring Security
到此這篇關(guān)于SpringSecurity中PasswordEncoder的使用的文章就介紹到這了,更多相關(guān)SpringSecurity PasswordEncoder內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA:Git stash 暫存分支修改的實現(xiàn)代碼
這篇文章主要介紹了IDEA:Git stash 暫存分支修改的實現(xiàn)代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03Spring Boot實現(xiàn)分布式鎖的自動釋放的示例代碼
在實際開發(fā)中,我們可以使用 Redis、Zookeeper 等分布式系統(tǒng)來實現(xiàn)分布式鎖,本文將介紹如何使用 Spring Boot 來實現(xiàn)分布式鎖的自動釋放,感興趣的朋友跟隨小編一起看看吧2023-06-06eclipse創(chuàng)建java項目并運(yùn)行的詳細(xì)教程講解
eclipse是java開發(fā)的ide工具,是大部分java開發(fā)人員的首選開發(fā)工具,可是對于一些新Java人員來說,不清楚eclipse怎么運(yùn)行項目?這篇文章主要給大家介紹了關(guān)于eclipse創(chuàng)建java項目并運(yùn)行的相關(guān)資料,需要的朋友可以參考下2023-04-04Spring Boot 中嵌入式 Servlet 容器自動配置原理解析
這篇文章主要介紹了Spring Boot 中嵌入式 Servlet 容器自動配置原理解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11Spring Boot環(huán)境下Mybatis Plus的快速應(yīng)用操作
這篇文章主要介紹了Spring Boot環(huán)境下Mybatis Plus的快速應(yīng)用操作,具有很好的價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11java警告:源發(fā)行版17 需要目標(biāo)發(fā)行版17問題及解決
文章介紹了如何解決項目JDK版本不一致的問題,包括修改Project Structure、Modules、Dependencies和Settings中的JDK版本,以及在pom.xml中指定JDK源版本2024-11-11當(dāng)面試官問我ArrayList和LinkedList哪個更占空間時,我是這么答的(面試官必問)
今天介紹一下Java的兩個集合類,ArrayList和LinkedList,這兩個集合的知識點幾乎可以說面試必問的。感興趣的朋友跟隨小編一起看看吧2020-08-08