---
# System prepended metadata

title: JwtAuthenticationConverter 與 @PreAuthorize
tags: [Spring]

---

## Spring Security：JwtAuthenticationConverter 與 @PreAuthorize 實戰筆記

- `JwtAuthenticationConverter`：決定如何把 JWT 的 claim 轉成 Spring Security 的 `Authentication`（特別是 authorities/roles）。
- `@PreAuthorize`：在方法層用 SpEL 表達式做授權判斷。

### JwtAuthenticationConverter 是什麼？
- 負責把 `Jwt` 物件轉成 `JwtAuthenticationToken`，同時決定 principal 與 authorities 的來源及格式。
- 預設會讀 `scope`/`scp` 並加上 `SCOPE_` 前綴；若你的 token 用 `roles`、`permissions` 等自訂 claim，就必須自訂 converter。
- 常見客製：
  - 改 authorities 的 claim 名稱（如 `roles`）。
  - 設定 authority 前綴（如 `ROLE_`；若要用 `hasAuthority('orders.read')` 可改為空字串）。
  - 指定 principal 來源（如 `sub` 或 `preferred_username`）。

### 自訂 JwtAuthenticationConverter 範例
```java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http,
      Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthConverter) throws Exception {
    http
      .authorizeHttpRequests(auth -> auth
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated()
      )
      .oauth2ResourceServer(oauth2 -> oauth2
        .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter))
      );
    return http.build();
  }

  @Bean
  JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
    authoritiesConverter.setAuthoritiesClaimName("roles"); // JWT 權限的 claim 名稱
    authoritiesConverter.setAuthorityPrefix("ROLE_");      // 讓 hasRole 判斷得通

    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
    converter.setPrincipalClaimName("sub"); // principal 來源，也可用 preferred_username
    return converter;
  }
}
```
說明：
- `roles` 內的值如 `"ADMIN"` 會被轉成 `ROLE_ADMIN`。
- 若想用細粒度權限（如 `orders.read`），可把 `setAuthorityPrefix("")`，改用 `hasAuthority('orders.read')`。

### @PreAuthorize 是什麼？
- 在方法執行前，用 SpEL 表達式檢查授權；常放在 Controller 或 Service。
- 先啟用方法安全：Spring Security 6+ 用 `@EnableMethodSecurity`，舊版可用 `@EnableGlobalMethodSecurity(prePostEnabled = true)`。
- 常用表達式：
  - `hasRole('ADMIN')`、`hasAnyRole('ADMIN','MANAGER')`
  - `hasAuthority('orders.read')`、`hasAnyAuthority(...)`
  - `authentication.name == #userId`（比對當事人）
  - `@beanName.check(authentication, #arg)`（呼叫自訂 Bean）

### @PreAuthorize 範例
```java
@RestController
@RequestMapping("/orders")
@EnableMethodSecurity
public class OrderController {

  // 需要 ROLE_ADMIN 或 ROLE_MANAGER
  @PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
  @GetMapping
  public List<Order> list() { ... }

  // 細粒度權限；若 converter 前綴為空可直接用 hasAuthority
  @PreAuthorize("hasAuthority('orders.read')")
  @GetMapping("/{id}")
  public Order get(@PathVariable Long id) { ... }

  // 只能讀自己的資料；principal 來自 JWT 的 sub
  @PreAuthorize("#userId == authentication.name")
  @GetMapping("/users/{userId}")
  public UserProfile findByUser(@PathVariable String userId) { ... }
}
```

### 整合與除錯心法
- 先決定 JWT 權限欄位格式，再設定 converter 的 claim 名稱與前綴；`@PreAuthorize` 要選 `hasRole` 還是 `hasAuthority` 取決於這個前綴。
- 若判斷不到權限，檢查：
  1) `jwtAuthenticationConverter` 是否已掛上，
  2) JWT claim 名稱是否正確，
  3) 權限值是否有預期的前綴（如 ROLE_）。
- 路由授權與方法授權可並用：路由控制粗粒度（需登入），方法標註處理細粒度（角色/權限/資料擁有者）。
