解決Spring security5.5.7報錯Encoded password does not look like BCrypt異常
背景
一個老項目,由于2022年爆發(fā)了spring bean和spring core的漏洞,將springboot從1.5.4升級到2.5.14版本,修復(fù)了spring的漏洞。
同時spring security也需要同步升級,升級過程中出現(xiàn)了一系列錯誤。
故做一個記錄在此。
問題:
登錄權(quán)限系統(tǒng)時,出現(xiàn)Encoded password does not look like BCrypt異常錯誤;同時報出clientSecret不匹配的問題。
解決方案
統(tǒng)一采用PasswordEncoderFactories.createDelegatingPasswordEncoder()去獲取到密碼加密器PasswordEncoder
(1)新版本的Spring Security對客戶端的密鑰進(jìn)行了加密處理,配置中需要使用PasswordEncoderFactories.createDelegatingPasswordEncoder().encode進(jìn)行加密;
(2)登錄成功后的handler,則需要采用PasswordEncoderFactories.createDelegatingPasswordEncoder().matches(clientSecret,clientDetails.getClientSecret())判斷密鑰是否匹配,而不是采用原有舊版的未加密的密鑰進(jìn)行equal進(jìn)行比較字符串。
BCryptPasswordEncoder介紹
Spring Security 中提供了 BCryptPasswordEncoder用于用戶密碼的加密和驗證,這里講解一下該 PasswordEncoder 的實現(xiàn)邏輯.
首先 BCryptPasswordEncoder 使用了 BCrypt 算法來對密碼實現(xiàn)加密和驗證。由于 BCrypt本身是一種 單向Hash算法,因此它和我們?nèi)粘S玫?MD5一樣,通常情況下是無法逆向解密的。
在 BSD系統(tǒng)中 BCrypt 算法主要用來替代 md5 加密算法,它使用了一種可變版本的Blowfish流密碼算法。通過多次加鹽和隨機(jī)數(shù),因此這套加密算法被廣泛用于許多系統(tǒng)的密碼加密當(dāng)中。
然而每次加密的結(jié)果是不一樣的,如果采用兩次加密的結(jié)果進(jìn)行equal比較,那是得不到真實的true結(jié)果的。
具體代碼改動
- 注冊一個bean,覆蓋原有的PasswordEncoder
/**
* 加密方式,spring security5.5.7升級后,默認(rèn)采用BCryptPasswordEncoder
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}- 項目啟動時,加載的認(rèn)證服務(wù)器配置的修改
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 引入BCrypt強(qiáng)哈希加密和解密工具
*/
@Autowired
private PasswordEncoder passwordEncoder;
/*
* 客戶端配置改動
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
//spirng boot 1.5.* 升級到spring boot 2.0以上,當(dāng)再次訪問授權(quán)服務(wù)器時出現(xiàn)Encoded password does not look like BCrypt異常,需要passwordEncoder.encode
if (ArrayUtils.isNotEmpty(securityProperties.getOauth2().getClients())) {
for (OAuth2ClientProperties config : securityProperties.getOauth2().getClients()) {
//設(shè)置clientid
builder.withClient(config.getClientId())
// 設(shè)置clientsecret,需要加密配置
.secret(passwordEncoder.encode(config.getClientSecret()))
// 設(shè)置令牌過期時間,單位秒,默認(rèn)7200,定義在OAuth2ClientProperties
.accessTokenValiditySeconds(config.getAccessTokenValidateSeconds())
// 允許的授權(quán)模式
.authorizedGrantTypes("refresh_token", "authorization_code", "password")
// 設(shè)置刷新令牌的過期時間,單位秒,這里設(shè)置為60天
.refreshTokenValiditySeconds(5184000)
// 配置oauth能獲取的權(quán)限,是一個數(shù)組
.scopes("all", "write", "read");
}
}
}- 構(gòu)造用戶登錄信息
@Component
public class MyUserDetailsService implements UserDetailsService {
/**
* 注入加密器
*/
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 搜索用戶信息,構(gòu)造登錄用戶
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//其他數(shù)據(jù)庫查詢等邏輯省略........
logger.info("表單登錄用戶名:" + username);
//進(jìn)行權(quán)限系統(tǒng)登錄,密碼前面需要加上加密的方式,調(diào)用createDelegatingPasswordEncoder后默認(rèn)會加上{bcrypt}在密碼前面
String password = passwordEncoder.encode("123456");
user.setLastLoginTime(nowTime);
sysPublicUserRepository.save(user);
logger.info("保存登錄時間:" + nowTime);
User user1 = new User(userId, password,
true, accountNonExpired, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));
return user1;
}
}- 登錄成功后SuccessHandler的校驗
@Component("authenticationSuccessHandler")
public class authenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
/**
* 注入密碼器
*/
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 此方法是用于在request中取得ClientDetails和新建tokenRequest,并用這兩個參數(shù)來生成OAuth2AccessToken
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//..............省略代碼
//..............
//客戶端的密鑰,從header中取出
String clientSecret = extractAndDecodeHeader(header, request);
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
if (clientDetails == null) {
throw new UnapprovedClientAuthenticationException("clientId對應(yīng)的配置信息不存在:" + clientId);
} else if (!passwordEncoder.matches(clientSecret,clientDetails.getClientSecret())) { //關(guān)鍵是這里不能用equal匹配了
throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
}
//............
//后續(xù)token的其他處理
//............
}
}總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- 使用spring?security?BCryptPasswordEncoder接入系統(tǒng)
- 如何在spring boot項目中使用Spring Security的BCryptPasswordEncoder類進(jìn)行相同密碼不同密文的加密和驗證
- 一文掌握SpringSecurity?BCrypt密碼加密和解密
- Springboot基于BCrypt非對稱加密字符串的實現(xiàn)
- SpringBoot整合BCrypt實現(xiàn)密碼加密
- Spring security BCryptPasswordEncoder密碼驗證原理詳解
- Spring項目使用Maven和BCrypt實現(xiàn)修改密碼功能方式
相關(guān)文章
Mybatis 如何批量刪除數(shù)據(jù)的實現(xiàn)示例
這篇文章主要介紹了Mybatis 如何批量刪除數(shù)據(jù)的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
關(guān)于MyBatis中映射對象關(guān)系的舉例
這篇文章主要介紹了關(guān)于MyBatis中映射對象關(guān)系的舉例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06
淺談如何優(yōu)雅地停止Spring Boot應(yīng)用
這篇文章主要介紹了淺談如何優(yōu)雅地停止Spring Boot應(yīng)用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
MyBatis基礎(chǔ)支持DataSource實現(xiàn)源碼解析
這篇文章主要為大家介紹了MyBatis基礎(chǔ)支持DataSource實現(xiàn)源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
揭秘springboot中Redisson?可重入鎖的實現(xiàn)原理
本文探究基于?Redisson?的可重入鎖原理,通過使用?hash?數(shù)據(jù)結(jié)構(gòu)?+?Lua?腳本實現(xiàn)可重入的分布式鎖,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-02-02
在Spring Boot中集成RabbitMQ的實戰(zhàn)記錄
本文介紹SpringBoot集成RabbitMQ的步驟,涵蓋配置連接、消息發(fā)送與接收,并對比兩種定義Exchange與隊列的方式:手動聲明(適合復(fù)雜路由)和注解綁定(適合快速開發(fā)),感興趣的朋友跟隨小編一起看看吧2025-06-06

