1. 簡介
藉助最新版本的 Spring Security,許多方面都發生了變化。其中之一是如何在我們的應用程序中處理密碼編碼。
在本教程中,我們將探索這些變化。
稍後,我們將瞭解如何配置新的委託機制,以及如何在不讓用户察覺的情況下更新我們的現有密碼編碼。
2. Spring Security 5.x 中的相關變更
Spring Security 團隊宣佈了 PasswordEncoder 在 org.springframework.security.authentication.encoding 中的已棄用。這是一個合理的舉動,因為舊接口並未為隨機生成的鹽而設計。因此,版本 5 中移除了此接口。此外,Spring Security 改變了處理編碼密碼的方式。在以前的版本中,每個應用程序僅使用一種密碼編碼算法。
默認情況下,StandardPasswordEncoder 處理了此操作。它使用 SHA-256 進行編碼。通過更改密碼編碼器,我們可以切換到另一種算法。但是,我們的應用程序必須堅持使用精確的算法。
版本 5.0 引入了密碼編碼委託的概念。
現在,我們可以為不同的密碼使用不同的編碼。Spring 通過在編碼密碼的開頭添加標識前綴來識別算法。
以下是 bcrypt 編碼密碼的示例:
{bcrypt}$2b$12$FaLabMRystU4MLAasNOKb.HUElBAabuQdX59RWHq5X.9Ghm692NEi
請注意,bcrypt 明確指定在花括號中。
3. 委託配置
如果密碼哈希沒有前綴,委託過程將使用默認編碼器。因此,默認情況下,我們得到 StandardPasswordEncoder
這使其與舊版 Spring Security 的默認配置兼容。
在版本 5 中,Spring Security 引入了 PasswordEncoderFactories.createDelegatingPasswordEncoder(). 此工廠方法返回一個配置的 DelegationPasswordEncoder 實例。
對於沒有前綴的密碼,該實例確保上述默認行為。對於包含前綴的密碼哈希,委託將相應地進行。
Spring Security 團隊列出了最新版本的受支持算法,請參見 相應的 JavaDoc。
當然,Spring 讓我們配置此行為。
假設我們想要支持:
- bcrypt 作為我們的默認值
- scrypt 作為替代方案
- SHA-256 作為當前使用的算法。
此配置集的外觀如下所示:
@Bean
public PasswordEncoder delegatingPasswordEncoder() {
PasswordEncoder defaultEncoder = new StandardPasswordEncoder();
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder(1, 1, 1, 1, 10));
DelegatingPasswordEncoder passworEncoder = new DelegatingPasswordEncoder(
"bcrypt", encoders);
passworEncoder.setDefaultPasswordEncoderForMatches(defaultEncoder);
return passworEncoder;
}
4. 遷移密碼編碼算法
在上一節中,我們探討了如何根據我們的需求配置密碼編碼。因此,現在我們將處理如何將已編碼的密碼切換到新的算法。
假設我們想將編碼從 SHA-256 更改為 bcrypt,但我們不想讓用户更改他們的密碼。
一個可能的解決方案是使用登錄請求。 在此時,我們可以訪問明文憑證。 那是我們可以取當前密碼並重新編碼的時刻。
因此,我們可以使用 Spring 的 AuthenticationSuccessEvent 用於此目的。 此事件在用户成功登錄我們的應用程序後觸發。
以下是示例代碼:
@Bean
public ApplicationListener<AuthenticationSuccessEvent>
authenticationSuccessListener( PasswordEncoder encoder) {
return (AuthenticationSuccessEvent event) -> {
Authentication auth = event.getAuthentication();
if (auth instanceof UsernamePasswordAuthenticationToken
&& auth.getCredentials() != null) {
CharSequence clearTextPass = (CharSequence) auth.getCredentials();
String newPasswordHash = encoder.encode(clearTextPass);
// [...] 更新用户密碼
((UsernamePasswordAuthenticationToken) auth).eraseCredentials();
}
};
}
在之前的片段中:
- 我們從提供的身份驗證詳細信息中檢索了用户密碼的明文版本
- 創建了使用新算法的新密碼哈希
- 從身份驗證令牌中刪除了明文密碼
默認情況下,由於 Spring Security 會盡快刪除明文密碼,因此提取密碼的明文是不可能的。
因此,我們需要配置 Spring 以保持明文密碼的版本。
此外,我們需要註冊我們的編碼委託:
@Configuration
public class PasswordStorageWebSecurityConfigurer {
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.eraseCredentials(false)
.userDetailsService(getUserDefaultDetailsService())
.passwordEncoder(passwordEncoder());
return authenticationManagerBuilder.build();
}
// ...
}
5. 結論
在本文中,我們介紹了 5.x 版本中可用的新密碼編碼功能。
我們還看到了如何配置多個密碼編碼算法來編碼我們的密碼。此外,我們還探索了一種更改密碼編碼的方法,而不會破壞現有的編碼。
最後,我們描述瞭如何使用 Spring 事件來透明地更新加密用户密碼,從而使我們能夠在不向用户披露的情況下無縫地更改我們的編碼策略。