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 註冊(或改密碼)

  1. 前端透過 HTTPS 傳 { username, password } 到後端
  2. 後端用 passwordEncoder.encode(password) 產生雜湊
  3. DB 只存雜湊值(不要存明文、也不要存可逆加密)

4.2 登入

  1. 前端透過 HTTPS 傳 { username, password }
  2. 後端查 DB 取出 encodedPassword
  3. 用 passwordEncoder.matches(password, encodedPassword) 驗證
  4. 驗證成功:建立 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. 你在專案中什麼時候會用到它?

  • 註冊(建立帳號)
  • 修改密碼(使用者自改)
  • 重設密碼(忘記密碼流程)
  • 登入驗證
  • 演算法/成本升級(漸進式升級)