--- 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..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: (미작성)