---
# System prepended metadata

title: PasswordEncoder 介紹與使用情境
tags: [Spring, Java]

---

# 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. 你在專案中什麼時候會用到它？

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

