PasswordEncoder 介紹與使用情境
PasswordEncoder 是 Spring Security 用來雜湊(hash)密碼與驗證密碼的核心介面。
重點:它不是用來「加密後再解密」的。
密碼應該只存 不可逆雜湊值,登入時用
matches()來比對。
1. 介面方法說明
1.1 String encode(CharSequence rawPassword)
- 用途:把「明文密碼」轉為「不可逆雜湊字串」。
- 特性:通常會包含 salt 與成本參數(例如 bcrypt 的 cost),因此每次 encode 的結果通常都不一樣。
- 使用時機:註冊、改密碼、重設密碼。
1.2 boolean matches(CharSequence rawPassword, String encodedPassword)
- 用途:驗證明文密碼是否正確。
- 作法:用
encodedPassword內含的演算法與參數,將rawPassword再雜湊一次並比對。 - 使用時機:登入驗證。
1.3 default boolean upgradeEncoding(String encodedPassword)
- 用途:判斷目前 stored hash 是否該升級(例如 bcrypt cost 太低,或計畫改用 argon2)。
- 使用時機:登入成功後,若需要升級則重新 encode 並更新 DB。
2. 為什麼密碼要用 PasswordEncoder
- 安全:避免使用不安全的雜湊方式(例如直接用 SHA-256 hash 密碼,抗暴力破解能力不足)。
- 一致性:所有密碼策略集中在後端控制(演算法、成本、升級策略)。
- 可演進:未來要升級演算法或成本參數,不需要一次重設所有人的密碼。
3. 常見實作與選擇
3.1 BCryptPasswordEncoder
- 業界最常見。
- 有可調的成本(cost / strength),數字越大越慢越安全。
3.2 Argon2PasswordEncoder
- 較新的選擇,可調記憶體/時間成本。
- 高安全需求場景常見。
3.3 Pbkdf2PasswordEncoder
- 也常用,但相對較舊。
3.4 DelegatingPasswordEncoder(Spring 推薦)
- 支援多種 hash 格式共存。
- 存在 DB 的字串會帶
{id}前綴,例如:{bcrypt}...、{argon2}... - 適合「舊系統搬遷 / 演算法升級」:不需要一次改完所有使用者密碼。
4. 正常業界的註冊與登入流程(使用情境)
4.1 註冊(或改密碼)
- 前端透過 HTTPS 傳
{ username, password }到後端 - 後端用
passwordEncoder.encode(password)產生雜湊 - DB 只存雜湊值(不要存明文、也不要存可逆加密)
4.2 登入
- 前端透過 HTTPS 傳
{ username, password } - 後端查 DB 取出
encodedPassword - 用
passwordEncoder.matches(password, encodedPassword)驗證 - 驗證成功:建立 Session 或發 JWT(Access/Refresh Token)
5. 範例:在 Spring Boot 設定 PasswordEncoder
5.1 最常見:bcrypt
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityBeans {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}5.2 推薦:DelegatingPasswordEncoder(可升級)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityBeans {
@Bean
public PasswordEncoder passwordEncoder() {
// 預設會用 bcrypt,並支援 {bcrypt} / {noop} / {pbkdf2} / {scrypt} / {argon2}... 等
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}6. 範例:註冊與登入時如何使用
6.1 註冊:encode 後存 DB
String encoded = passwordEncoder.encode(request.getPassword());
user.setPassword(encoded);
userRepository.save(user);6.2 登入:matches 驗證
User user = userRepository.findByUsername(request.getUsername())
.orElseThrow(() -> new RuntimeException("帳號不存在"));
boolean ok = passwordEncoder.matches(request.getPassword(), user.getPassword());
if (!ok) {
throw new RuntimeException("密碼錯誤");
}7. 常見踩雷與注意事項
7.1 不要用 encode(raw).equals(encoded) 來驗證
因為 encode 會用 salt,結果通常每次都不同。
正確做法:一定要用 matches()。
7.2 不要用 NoOpPasswordEncoder
{noop} 代表不做任何處理(等同存明文),只適合測試或 demo。
7.3 密碼永遠不要寫入 log
包含:後端 request log、前端埋點、APM、Nginx access log 自訂欄位。
7.4 演算法升級策略
- 可以用
upgradeEncoding(encodedPassword)判斷是否需要升級 - 登入成功後若需要升級:重新 encode 並更新 DB
8. 你在專案中什麼時候會用到它?
- 註冊(建立帳號)
- 修改密碼(使用者自改)
- 重設密碼(忘記密碼流程)
- 登入驗證
- 演算法/成本升級(漸進式升級)