Files
llm-wiki/raw/official-docs/spring-security-resource-server-jwt.md

170 lines
14 KiB
Markdown

---
title: Spring Security — OAuth 2.0 Resource Server JWT (issuer-uri, JwtDecoder)
source_type: official-doc
url: https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html
archive_url:
related_projects: [keycloak-patterns]
related_branches: [feature-keycloak-patterns, feature-keycloak-single-ec2-no-google, feature-keycloak-spring-rs-audience-validator, feature-keycloak-spring-rs-role-mapping, feature-keycloak-iss-claim-hostname-mismatch]
tags: [audience-validator, jwks, jwt-validation, keycloak-patterns, oauth2, oidc, p2a-spa-resource-server, p3a-single-ec2, resource-server, spring-boot, spring-security, official-doc]
status: raw
confidence: high
created: 2026-05-25
last_reviewed: 2026-05-27
---
# Spring Security — OAuth 2.0 Resource Server JWT (issuer-uri, JwtDecoder)
> Layer: `raw/official-docs/` — Spring Security Reference / "OAuth 2.0 Resource Server / JWT" 페이지 verbatim.
> P2A/P3A 의 Spring Boot Resource Server (`/api/me` 등) 가 Keycloak JWT 를 검증하는 최소 설정 + audience/role 매핑 customize 의 1차 근거.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-keycloak-patterns]] | keycloak-patterns root — Resource Server 가 `issuer-uri` 한 줄로 OIDC discovery → JWKS 검증을 자동 구성한다는 사실 |
| [[raw/branch-notes/feature-keycloak-single-ec2-no-google]] | P3A 단일 EC2 학습 환경 backend (`application.yml`) 의 `spring.security.oauth2.resourceserver.jwt.issuer-uri` 설정 근거 |
| [[raw/branch-notes/feature-keycloak-spring-rs-audience-validator]] | `audiences` property 로 `aud` claim 검증 추가 결정 근거 (token 이 의도된 RS 로 발급되었는지) |
| [[raw/branch-notes/feature-keycloak-spring-rs-role-mapping]] | Keycloak realm role → Spring `GrantedAuthority` 매핑 시 `JwtAuthenticationConverter` + `SCOPE_` prefix 의 default 동작 근거 |
| [[raw/branch-notes/feature-keycloak-iss-claim-hostname-mismatch]] | `issuer-uri` 와 token `iss` 가 정확 일치해야 한다는 startup expectation 의 근거 (Keycloak `KC_HOSTNAME` 함정) |
## 컨텍스트
P2A/P3A 의 backend (`/api/me`) 는 Spring Boot 3.x + spring-security-oauth2-resource-server 로 구현. `issuer-uri` 한 줄로 OIDC discovery + JWKS 자동 fetch + `iss` 검증이 동작한다는 점이 Keycloak 통합의 최소 구성 단위. `audience` 검증 + 권한 추출 customize 는 keycloak realm role 매핑의 1차 근거.
## 출처 / Source
- 원본 URL: https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html
- 아카이브 URL: (미수집)
- 저자 / 조직: Spring Security (VMware / Broadcom)
- 발행일: rolling docs (current = 6.x)
- 마지막 확인일: 2026-05-27
## 핵심 인용 / Key quotes (verbatim)
> [§Specifying the Authorization Server] "Where `idp.example.com/issuer` is the value contained in the `iss` claim for JWT tokens that the authorization server will issue. Resource Server will use this property to further self-configure, discover the authorization server's public keys, and subsequently validate incoming JWTs."
> [§Startup Expectations] "It achieves this through a deterministic discovery process it launches at the first request containing a JWT: 1. Query the Provider Configuration or Authorization Server Metadata endpoint for the `jwks_url` property 2. Query the `jwks_url` endpoint for supported algorithms 3. Configure the validation strategy to query `jwks_url` for valid public keys of the algorithms found 4. Configure the validation strategy to validate each JWTs `iss` claim against `idp.example.com`."
> [§How JWT Authentication Works] "`JwtAuthenticationProvider` is an `AuthenticationProvider` implementation that leverages a `JwtDecoder` and `JwtAuthenticationConverter` to authenticate a JWT."
> [§Configuring Authorization] "A JWT that is issued from an OAuth 2.0 Authorization Server will typically either have a `scope` or `scp` attribute, indicating the scopes (or authorities) it's been granted, for example: `{ …, \"scope\" : \"messages contacts\"}`"
> [§Configuring Authorization] "When this is the case, Resource Server will attempt to coerce these scopes into a list of granted authorities, prefixing each scope with the string \"SCOPE_\"."
> [§Specifying the Authorization Server JWK Set Uri Directly] "If the authorization server doesn't support any configuration endpoints, or if Resource Server must be able to initialize independently from the authorization server, then the `jwk-set-uri` can be supplied as well"
> [§Specifying the Authorization Server JWK Set Uri Directly] "Consequently, Resource Server will not ping the authorization server at startup. We still specify the `issuer-uri` so that Resource Server still validates the `iss` claim on incoming JWTs."
> [§Supplying Audiences] "Boot also has the `audiences` property for validating the `aud` claim; this is who the JWT was sent to."
> [§Supplying Audiences] "The result will be that if the JWT's `iss` claim is not `idp.example.com`, and its `aud` claim does not contain `my-resource-server.example.com` in its list, then validation will fail."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| SSRS-JWT-C1 | `issuer-uri` property 는 token `iss` 값이어야 하며, Resource Server 는 이 값으로 self-configure (authorization server public key discovery + JWT 검증) | [§Specifying the Authorization Server] "Where `idp.example.com/issuer` is the value contained in the `iss` claim for JWT tokens that the authorization server will issue. Resource Server will use this property to further self-configure, discover the authorization server's public keys, and subsequently validate incoming JWTs." | `official-vendor-doc` | Spring Security 6.x + OAuth2 Resource Server (servlet) | `issuer-uri` 가 Keycloak 의 `KC_HOSTNAME` 과 어떻게 매핑되는지의 정확한 형태는 본 인용 범위 밖 (Keycloak 별도 문서) |
| SSRS-JWT-C2 | startup 시 첫 JWT 요청에서 deterministic discovery 4단계 수행: (1) `jwks_url` 조회 (2) supported algorithms 확인 (3) JWKS 로 public key 검증 strategy 구성 (4) `iss` claim 을 `issuer-uri` 와 비교 | [§Startup Expectations] "It achieves this through a deterministic discovery process it launches at the first request containing a JWT: 1. Query the Provider Configuration or Authorization Server Metadata endpoint for the `jwks_url` property 2. Query the `jwks_url` endpoint for supported algorithms 3. Configure the validation strategy to query `jwks_url` for valid public keys of the algorithms found 4. Configure the validation strategy to validate each JWTs `iss` claim against `idp.example.com`." | `official-vendor-doc` | Boot auto-configuration 사용 시 | discovery 실패 시 retry/backoff 정책은 본 인용 범위 밖 |
| SSRS-JWT-C3 | `JwtAuthenticationProvider``JwtDecoder` + `JwtAuthenticationConverter` 를 사용해 JWT 를 인증하는 `AuthenticationProvider` 구현체 | [§How JWT Authentication Works] "`JwtAuthenticationProvider` is an `AuthenticationProvider` implementation that leverages a `JwtDecoder` and `JwtAuthenticationConverter` to authenticate a JWT." | `official-vendor-doc` | Spring Security JWT authentication chain | reactive (WebFlux) 변형의 클래스명은 본 인용 범위 밖 |
| SSRS-JWT-C4 | JWT 의 `scope`/`scp` claim 의 각 scope 는 default 로 `SCOPE_` prefix 가 붙은 granted authority 로 변환 | [§Configuring Authorization] "When this is the case, Resource Server will attempt to coerce these scopes into a list of granted authorities, prefixing each scope with the string \"SCOPE_\"." | `official-vendor-doc` | default `JwtAuthenticationConverter` 사용 시 | Keycloak realm role (`realm_access.roles`) 이 default 로 자동 매핑된다는 뜻은 **아님** — Keycloak role 매핑은 `JwtGrantedAuthoritiesConverter``setAuthoritiesClaimName` customize 필요 |
| SSRS-JWT-C5 | `jwk-set-uri` 를 직접 지정 가능; 이 경우 startup 시 authorization server ping 안 함. 단 `issuer-uri` 는 여전히 명시 (token `iss` 검증을 위해) | [§Specifying the Authorization Server JWK Set Uri Directly] "If the authorization server doesn't support any configuration endpoints, or if Resource Server must be able to initialize independently from the authorization server, then the `jwk-set-uri` can be supplied as well" + "Consequently, Resource Server will not ping the authorization server at startup. We still specify the `issuer-uri` so that Resource Server still validates the `iss` claim on incoming JWTs." | `official-vendor-doc` | authorization server 가 OIDC discovery 미지원 또는 RS 가 독립 부팅 요구 | `jwk-set-uri` 단독 (issuer 없음) 사용 시 동작은 본 인용 범위 밖 |
| SSRS-JWT-C6 | Boot 의 `audiences` property 는 `aud` claim 검증을 활성화; `iss` 또는 `aud` 어느 하나라도 불일치 시 검증 실패 | [§Supplying Audiences] "Boot also has the `audiences` property for validating the `aud` claim; this is who the JWT was sent to." + "The result will be that if the JWT's `iss` claim is not `idp.example.com`, and its `aud` claim does not contain `my-resource-server.example.com` in its list, then validation will fail." | `official-vendor-doc` | Boot auto-configuration + `audiences` property 사용 | programmatic `aud` validator (별도 `OAuth2TokenValidator`) 의 정확한 추가 방식은 본 인용 범위 밖 — 별도 §Configuring Validation 페이지 참조 |
## Usage Boundaries / 적용 경계
- **이 자료가 직접 증명하는 것**:
- `SSRS-JWT-C1`: `issuer-uri` 한 줄로 OIDC discovery + JWKS + `iss` 검증이 자동화된다는 사실
- `SSRS-JWT-C2`: 4단계 deterministic discovery 의 정확한 순서
- `SSRS-JWT-C3`: `JwtAuthenticationProvider` 의 두 협력자 (`JwtDecoder`, `JwtAuthenticationConverter`) 의 정확한 명명
- `SSRS-JWT-C4`: `scope`/`scp` claim → `SCOPE_` prefix 자동 변환의 default 동작
- `SSRS-JWT-C5`: `jwk-set-uri` 직접 지정 + `issuer-uri` 병기 패턴
- `SSRS-JWT-C6`: `audiences` property 의 `aud` 검증 활성화 + 실패 조건
- **이 자료가 증명하지 않는 것**:
- Keycloak `realm_access.roles` claim 이 default 로 `ROLE_` prefix 의 granted authority 로 매핑된다는 뜻 — Keycloak realm role 은 `scope`/`scp` 가 아닌 별도 nested claim 이므로 `JwtAuthenticationConverter` customize 필수
- opaque token introspection (별도 `/oauth2/introspection` 페이지)
- `issuer-uri` 가 Keycloak 의 `KC_HOSTNAME` 변경 시 자동 추적된다는 뜻 — startup 시점에 한 번만 discovery
- WebFlux/reactive 환경의 클래스명 (`ReactiveJwtDecoder` 등) 의 정확한 매핑
- **내 프로젝트에 적용하려면 추가 확인이 필요한 것**:
- P3A 의 `KC_HOSTNAME=localhost` 와 backend `issuer-uri=http://localhost:8080/realms/keycloak-patterns` 일치 검증 (token 의 `iss` 가 정확히 동일한지 token 디코딩 확인)
- Keycloak realm role 을 `ROLE_` prefix 의 authority 로 매핑하는 `JwtGrantedAuthoritiesConverter` 의 정확한 `setAuthoritiesClaimName` 값 (`realm_access.roles` vs `resource_access.<client>.roles`)
- `audiences` property 가 string list 인지 single string 인지 (Boot 3.x property binding 형식)
## 최소 설정 (해석 — 내 프로젝트 메모)
> 본 섹션은 자료 직접 인용 아님. P3A 적용 가이드.
```yaml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/realms/keycloak-patterns
```
- 첫 요청 시 위 `SSRS-JWT-C2` 의 4단계 deterministic discovery 자동 수행.
## JWKS URI 직접 지정 (해석)
```yaml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/realms/keycloak-patterns
jwk-set-uri: http://localhost:8080/realms/keycloak-patterns/protocol/openid-connect/certs
```
## Audience 검증 (해석)
```yaml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/realms/keycloak-patterns
audiences: keycloak-patterns-backend
```
## 권한 추출 customize (해석)
```java
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter conv = new JwtGrantedAuthoritiesConverter();
conv.setAuthoritiesClaimName("realm_access.roles"); // Keycloak realm role
conv.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jac = new JwtAuthenticationConverter();
jac.setJwtGrantedAuthoritiesConverter(conv);
return jac;
}
```
## P3A 적용 메모 (내 프로젝트 해석)
> 본 섹션은 자료 직접 인용 아님. P3A 결정 컨텍스트 해석.
- `issuer-uri`**Keycloak 이 발급한 token 의 `iss` 와 정확히 동일해야 함** (`SSRS-JWT-C1` + `C2.4`) → Keycloak `KC_HOSTNAME=localhost` 라면 backend 도 `http://localhost:8080/realms/...`.
- `/api/me` 등 endpoint 는 `.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))` 한 줄로 보호.
- `@AuthenticationPrincipal Jwt jwt` 로 컨트롤러에서 claim 접근 → `jwt.getClaimAsString("preferred_username")`.
## 한계 / 후속
- 본 문서는 JWT validation 만. opaque token introspection 은 별도 페이지.
- 본 wiki 변환 시 `wiki/concepts/spring-security-resource-server-jwt` 후보.
## Related / 관련
- 같은 주제 다른 official-doc:
- [[raw/official-docs/keycloak-hostname-configuration]] (issuer 일치 함정)
- [[raw/official-docs/security-jwt-rfc-7519-validation]]
- [[raw/official-docs/security-oauth2-pkce-rfc-8252]]
- 인용하는 branch:
- [[raw/branch-notes/feature-keycloak-patterns]]
- [[raw/branch-notes/feature-keycloak-single-ec2-no-google]]
- [[raw/branch-notes/feature-keycloak-spring-rs-audience-validator]]
- [[raw/branch-notes/feature-keycloak-spring-rs-role-mapping]]
- 인용하는 wiki: (미작성)