88 lines
13 KiB
Markdown
88 lines
13 KiB
Markdown
---
|
||
title: official-doc / Spring Security — OAuth2 Authorized Client Features (Servlet)
|
||
source_type: official-doc
|
||
url: https://docs.spring.io/spring-security/reference/servlet/oauth2/client/authorized-clients.html
|
||
archive_url:
|
||
related_branches: [feature-keycloak-bff-oauth2login-session]
|
||
related_projects: []
|
||
tags: [official-doc, keycloak-patterns, auth, spring-security, oauth2]
|
||
created: 2026-07-24
|
||
---
|
||
|
||
# official-doc / Spring Security — OAuth2 Authorized Client Features (Servlet)
|
||
|
||
> Layer: `raw/official-docs/` — Spring Security 공식 레퍼런스(Servlet 스택) "Authorized Client Features" 페이지의 원문 발췌. `feature-keycloak-bff-oauth2login-session` branch(WI-010)의 "BFF는 access/refresh token을 server-side에 보관하고, downstream Resource API 호출 시 held access token을 Bearer로 첨부한다"는 결정의 공식 벤더 근거로 보관.
|
||
|
||
## Parent / 활용 branch (필수)
|
||
|
||
| Branch | 이 자료가 정당화하는 결정 |
|
||
|---|---|
|
||
| [[raw/branch-notes/feature-keycloak-bff-oauth2login-session]] | AP3(BFF) 패턴에서 (0) `@RegisteredOAuth2AuthorizedClient` 메서드 인자 리졸버 또는 `OAuth2AuthorizedClientManager`로 서버측 보유 `OAuth2AuthorizedClient`(token holder)를 조회(retrieve)하고, (1) `OAuth2AuthorizedClient`가 principal에 scope되어 server-side에 저장되며, (2) `OAuth2ClientHttpRequestInterceptor`(RestClient) / `ServletOAuth2AuthorizedClientExchangeFilterFunction`(WebClient)이 저장된 access token을 outbound resource 요청의 Bearer 헤더로 첨부하고, (3) 만료된 access token을 자동으로 refresh한다는 4가지 완료 조건 세부사항의 공식 벤더 근거 |
|
||
|
||
## 출처
|
||
|
||
- 원본 URL: https://docs.spring.io/spring-security/reference/servlet/oauth2/client/authorized-clients.html
|
||
- 아카이브 URL: (미수집)
|
||
- 저자 / 조직: Spring Security (VMware/Broadcom) — 공식 레퍼런스 문서
|
||
- 발행일: 고정 발행일 없음 (rolling reference doc, 버전별 URL 존재: 6.5 / 7.0 / 7.1-SNAPSHOT 등)
|
||
- 마지막 확인일: 2026-07-24
|
||
|
||
## 왜 저장했는지
|
||
|
||
branch `feature-keycloak-bff-oauth2login-session`의 완료 조건은 "browser token 0개와 SESSION cookie만으로 BFF proxy API 200이 재현된다"이다. 이를 구현하려면 Spring Security가 서버측에 보관된 `OAuth2AuthorizedClient`를 (a) 어떻게 조회하고, (b) 그것을 어떻게 principal에 scope하여 저장하며, (c) downstream Resource API 호출 시 보유 access token을 어떻게 꺼내 Bearer로 첨부하고, (d) 만료 시 어떻게 자동 갱신하는지에 대한 공식 근거가 필요하다. 이 페이지는 `@RegisteredOAuth2AuthorizedClient`/`OAuth2AuthorizedClientManager`(조회), `PrincipalResolver`(저장·스코프), `OAuth2ClientHttpRequestInterceptor`/`ServletOAuth2AuthorizedClientExchangeFilterFunction`(첨부), 자동 refresh를 모두 다루는 유일한 단일 공식 페이지다.
|
||
|
||
## 핵심 인용
|
||
|
||
> [§Resolving an Authorized Client] "The @RegisteredOAuth2AuthorizedClient annotation is handled by OAuth2AuthorizedClientArgumentResolver, which directly uses an OAuth2AuthorizedClientManager and, therefore, inherits its capabilities."
|
||
|
||
> [§RestClient Integration › Providing the principal] "OAuth2ClientHttpRequestInterceptor uses a PrincipalResolver to determine which principal name is associated with the access token, which allows an application to choose how to scope the OAuth2AuthorizedClient that is stored."
|
||
|
||
> [§RestClient Integration] "This interceptor provides the ability to make protected resources requests by placing a Bearer token in the Authorization header of an outbound request."
|
||
|
||
> [§WebClient Integration for Servlet Environments] "The ServletOAuth2AuthorizedClientExchangeFilterFunction provides a mechanism for requesting protected resources by using an OAuth2AuthorizedClient and including the associated OAuth2AccessToken as a Bearer Token."
|
||
|
||
> [§RestClient Integration] "If an existing OAuth2AccessToken is expired, it is refreshed (or renewed)"
|
||
|
||
## 추출된 주장 (Claims Extracted)
|
||
|
||
> `Claim ID` prefix: `SPRING-AUTHZCLIENT`.
|
||
|
||
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|
||
|---|---|---|---|---|---|
|
||
| SPRING-AUTHZCLIENT-C1 | `@RegisteredOAuth2AuthorizedClient` 애노테이션은 `OAuth2AuthorizedClientArgumentResolver`가 처리하며, 이 리졸버는 `OAuth2AuthorizedClientManager`를 직접 사용해 서버측 보유 `OAuth2AuthorizedClient`(token holder)를 **조회(retrieve)**한다 | [§Resolving an Authorized Client] "The @RegisteredOAuth2AuthorizedClient annotation is handled by OAuth2AuthorizedClientArgumentResolver, which directly uses an OAuth2AuthorizedClientManager and, therefore, inherits its capabilities." | `official-vendor-doc` | 컨트롤러 메서드 파라미터에 `@RegisteredOAuth2AuthorizedClient("registrationId")`를 선언해 `OAuth2AuthorizedClient`를 얻는 표준 조회 경로 | `OAuth2AuthorizedClientService`를 이용한 대안 조회 경로(HttpServletRequest 컨텍스트 밖)의 상세 동작까지는 이 문장이 서술하지 않음 — annotation→ArgumentResolver→Manager 위임 관계만 직접 증명 |
|
||
| SPRING-AUTHZCLIENT-C2 | `OAuth2ClientHttpRequestInterceptor`는 principal 이름을 access token과 연결(associate)하는 `PrincipalResolver`를 사용하며, 이를 통해 애플리케이션이 **저장되는(stored)** `OAuth2AuthorizedClient`의 scope(누구 소유인지)를 선택할 수 있다 | [§Providing the principal] "OAuth2ClientHttpRequestInterceptor uses a PrincipalResolver to determine which principal name is associated with the access token, which allows an application to choose how to scope the OAuth2AuthorizedClient that is stored." | `official-vendor-doc` | RestClient + `OAuth2ClientHttpRequestInterceptor` 경로, 기본 `SecurityContextHolderPrincipalResolver` 또는 `RequestAttributePrincipalResolver` 구성 | 저장 매체(`HttpSession` 등 구체 구현체)는 이 문장이 명시하지 않음 — "저장된다(is stored)"는 사실과 principal-scope 사실만 증명. 브라우저가 access token을 절대 보유하지 않는다는 명제 자체를 이 문장이 직접 서술하지는 않음(그 명제는 branch 상속 결정 `DEC-KEYCLOAK-PATTERNS-OVERVIEW-BFF-SESSION-001@1`의 범위이며, 이 자료는 그 설계와 정합적인 "서버측 저장·스코프" 사실만 뒷받침) |
|
||
| SPRING-AUTHZCLIENT-C3 | RestClient용 `OAuth2ClientHttpRequestInterceptor`는 outbound request의 `Authorization` 헤더에 Bearer 토큰을 배치해 보호된 리소스 요청을 가능하게 한다 | [§RestClient Integration] "This interceptor provides the ability to make protected resources requests by placing a Bearer token in the Authorization header of an outbound request." | `official-vendor-doc` | `RestClient` + `OAuth2AuthorizedClientManager` 기반 구성(`RestClientConfig` 예제) | WebClient 경로에는 적용되지 않음(별개 클래스 — C4 참조). 캐시된 access token을 우선 재사용하는지, 매 요청마다 토큰 조회 비용이 있는지 등 내부 캐싱 세부사항은 규정하지 않음 |
|
||
| SPRING-AUTHZCLIENT-C4 | `ServletOAuth2AuthorizedClientExchangeFilterFunction`은 `OAuth2AuthorizedClient`를 사용해 보호된 리소스를 요청하는 메커니즘을 제공하며, 연관된 `OAuth2AccessToken`을 Bearer Token으로 포함시킨다 | [§WebClient Integration for Servlet Environments] "The ServletOAuth2AuthorizedClientExchangeFilterFunction provides a mechanism for requesting protected resources by using an OAuth2AuthorizedClient and including the associated OAuth2AccessToken as a Bearer Token." | `official-vendor-doc` | Servlet 환경에서 WebClient + `ExchangeFilterFunction` 구성 — BFF가 downstream Resource API를 WebClient로 호출하는 proxy fan-out 시나리오의 직접 근거 | `@RegisteredOAuth2AuthorizedClient`로 명시 전달하거나 `clientRegistrationId` 속성으로 지정한 client에 한정된 서술 — `setDefaultOAuth2AuthorizedClient(true)` "default" 동작(모든 요청에 동일 principal 토큰이 첨부되는 위험)은 이 문장이 아니라 별도 경고 문단("Be cautious…")의 범위이며 이 claim으로 그 위험까지 정당화하지 않음 |
|
||
| SPRING-AUTHZCLIENT-C5 | 기존 `OAuth2AccessToken`이 만료된 경우, (RestClient interceptor 경로에서) 자동으로 refresh(또는 renew)된다 | [§RestClient Integration] "If an existing OAuth2AccessToken is expired, it is refreshed (or renewed)" | `official-vendor-doc` | `OAuth2AuthorizedClientManager`가 refresh 수행 가능한 `OAuth2AuthorizedClientProvider`(예: refresh-token provider)를 구성한 RestClient 요청 경로 | refresh_token 자체가 만료·폐기되어 refresh 시도가 실패하는 경우의 동작은 이 문장이 규정하지 않음(그 경우는 "Handling Failure"/`OAuth2AuthorizationFailureHandler`의 별개 관심사이며 이 raw 문서에는 발췌 대상 quote로 포함하지 않았다 — §관련 참조). WebClient 경로의 동일 자동 refresh 문구("if an OAuth2AuthorizedClientProvider is available to perform the authorization")는 이 인용과 다른 문장이며 이 claim 범위 밖 |
|
||
|
||
## 적용 경계 (Usage Boundaries)
|
||
|
||
- 이 자료가 직접 증명하는 것:
|
||
- `SPRING-AUTHZCLIENT-C1`: `@RegisteredOAuth2AuthorizedClient` 애노테이션 → `OAuth2AuthorizedClientArgumentResolver` → `OAuth2AuthorizedClientManager` 위임 관계로 서버측 `OAuth2AuthorizedClient`를 조회하는 공식 메커니즘 존재
|
||
- `SPRING-AUTHZCLIENT-C2`: authorized client가 principal에 scope되어 "저장된다"는 사실
|
||
- `SPRING-AUTHZCLIENT-C3`, `C4`: RestClient/WebClient 각각에서 저장된 access token을 Bearer 헤더로 첨부하는 공식 메커니즘 존재
|
||
- `SPRING-AUTHZCLIENT-C5`: 만료된 access token의 자동 refresh(renew) 동작
|
||
- 이 자료가 증명하지 않는 것 (**UNSUPPORTED_DECISION 후보** — branch 구현 가이드에 그대로 인용 금지, 아래 세부사항은 branch-spec 단계에서 근거 보강 또는 라벨링 필요):
|
||
- **"browser 는 access/refresh token 을 절대 보유하지 않는다"는 명제 자체** — 이 페이지는 서버측 조회·저장·스코프 메커니즘만 서술할 뿐, 브라우저가 무엇을 받는지(session cookie만인지)는 전혀 언급하지 않는다. 이 명제는 `oauth2Login()`의 자체 동작(어떤 응답에 무엇을 내려주는지)에 대한 별도 공식 근거(`servlet/oauth2/login/` 섹션)가 필요하다.
|
||
- `OAuth2AuthorizedClientRepository`/`OAuth2AuthorizedClientService`의 기본 구현(예: `HttpSessionOAuth2AuthorizedClientRepository`)이 실제로 `HttpSession`에 저장한다는 클래스명·직렬화 방식 — 이 페이지 범위 밖(별도 core/Javadoc 페이지 소관). 본 raw 자료는 "Handling Failure" 절의 `OAuth2AuthorizedClientRepository`/`Service` 구분 예제를 포함하지만, 그 절의 문장을 quote로 발췌하지 않았으므로 Repository vs Service의 request-scope 차이는 claim으로 등재하지 않는다.
|
||
- refresh token 자체의 rotation 정책(1회용 여부, 재사용 감지) — 이 페이지는 access token 갱신 트리거만 서술하고 refresh token 자체의 lifecycle은 다루지 않음.
|
||
- 내 프로젝트(BFF, WI-010)에 적용하려면 추가 확인이 필요한 것:
|
||
- 실제 `OAuth2AuthorizedClientRepository`/`Service` 기본 구현이 `HttpSession`을 사용하는지 core.html/Javadoc으로 재확인.
|
||
- `oauth2Login()`이 인증 성공 시 브라우저에 실제로 무엇을 내려주는지(세션 쿠키만인지) 별도 공식 문서로 확인.
|
||
- BFF의 실제 proxy controller가 `@RegisteredOAuth2AuthorizedClient` + `RestClient`/`WebClient` 중 어떤 조합을 쓸지, 그리고 조회한 `OAuth2AuthorizedClient`를 어느 인터셉터/필터에 넘길지 branch-spec 단계에서 결정.
|
||
|
||
## 메모
|
||
|
||
> 검증되지 않은 내 해석. 사실 인용과 분리.
|
||
|
||
- C1이 "조회(retrieve)" 근거, C2가 "저장·스코프" 근거로 D2(토큰 holder) 결정을 함께 뒷받침한다(미검증 — branch-spec 작성 시 재확인).
|
||
- C3/C4 조합이 "held access token을 Bearer로 첨부해 downstream Resource API를 proxy"하는 구현 방식의 두 가지 후보(RestClient vs WebClient)를 제공한다. 어느 쪽을 BFF proxy 구현에 쓸지는 branch-spec 단계의 별도 결정 필요.
|
||
- C5가 "자동 refresh" 완료 조건의 직접 근거이나, refresh token 자체가 만료된 edge case는 "Handling Failure" 절(본 raw 자료에는 quote 미등재)과 별도로 다뤄야 한다.
|
||
- WebFetch(AI 요약 도구)의 1차 결과는 paraphrase되어 있어 verbatim 인용 소스로 부적합했다 — 원본 HTML을 직접 fetch(`curl`)하여 태그를 제거한 순수 텍스트를 `source-fetch.txt`로 사용했다(원문 바이트 보존, 요약 아님).
|
||
- 추가로 봐야 할 동일 출처 페이지: `servlet/oauth2/client/core.html`("Core Interfaces and Classes" — `OAuth2AuthorizedClientRepository`/`Service` 기본 구현체 정의 추정), `servlet/oauth2/login/index.html`(oauth2Login이 브라우저에 내려주는 것).
|
||
|
||
## 관련
|
||
|
||
- 같은 주제 다른 official-doc / company-tech-blog: (아직 없음 — 이 branch의 최초 OAuth2 Authorized Client raw 자료)
|
||
- 이 자료를 인용한 wiki 요약: `[[wiki/concepts/...]]` (생성 시)
|