# 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 ```java 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(可升級) ```java 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 ```java String encoded = passwordEncoder.encode(request.getPassword()); user.setPassword(encoded); userRepository.save(user); ``` ### 6.2 登入:matches 驗證 ```java 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. 你在專案中什麼時候會用到它? * 註冊(建立帳號) * 修改密碼(使用者自改) * 重設密碼(忘記密碼流程) * 登入驗證 * 演算法/成本升級(漸進式升級)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up