docs(keycloak): adopt the decomposition contract, fix the redirect URI, strip evaluative prose

- 계약 채택 — 독자 질문, 후보 29건(PROMOTE 24 · MERGE_INTO 4 · KEEP_IN_SSOT 1). 게시 중
  17건은 전부 유지. 저장소 keycloak-pattern 은 패턴 넷이 브랜치로 갈라져 있어 revisions 로
  tip 넷을 적었다. keycloak-session-store 는 같은 저장소 @ cdac9b8
- 게시된 기록의 redirect_uri 가 SSOT·코드와 달랐다 — OAuth2callback.html → callback.html
  (frontend/src/app.js 에서 확인). 계약 title 이 기록과 다른 7건도 기록 쪽으로 맞췄다
- 미작성 1건 작성 — 패턴 검증을 실제로 돌릴 때의 안전한 순서(Reference)
- 리뷰 100건 반영 — 설명 뒤에 붙은 평가·차례 예고·독자 오해 가정·작성 지시를 지웠다.
  삭제가 남긴 조각 4건을 고치고, 원래부터 잘려 있던 로컬 미리보기 라벨 1건도 닫았다

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-07 12:39:20 +09:00
co-authored by Claude Fable 5.1
parent 62520a4dce
commit 4d50bb939a
26 changed files with 1947 additions and 1225 deletions
@@ -12,50 +12,54 @@ decisionStatus: PROPOSED
decidedOn: 2026-08-31
studio: "https://hyeonworks.com/studio/documents/19b55c39-c583-4161-9775-df954280a568/edit"
public: "https://hyeonworks.com/projects/keycloak-patterns/decisions/bff-owns-token-when-browser-must-not"
sourceRevision: keycloak-patterns-lab@2026-08
source:
- final/document.md#선택의-이유와-지킨-경계-ap3
- final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap3
---
# BFF가 OAuth Token을 관리하는 조건
애플리케이션 계층에서 API 응답 조합과 인가를 처리하면서도 브라우저 JavaScript에는 OAuth Token을 노출하지 않아야 한다면 BFF 구조를 선택할 수 있다.
BFF(Backend for Frontend)는 화면에 필요한 API를 브라우저 대신 호출하고 결과만 돌려주는 백엔드다. 애플리케이션 계층에서 API 응답 조합과 인가를 처리하면서도 브라우저 JavaScript에는 OAuth 토큰을 노출하지 않아야 한다면 구조를 선택할 수 있다.
이 경우 BFF가 Authorization Code를 Token으로 교환하고, Access Token과 Refresh Token을 서버에 보관한다.
브라우저는 OAuth Token 대신 Application Session을 이용해 BFF를 호출하고,
BFF는 저장된 Access Token으로 Downstream Resource Server를 호출한다.
이 경우 BFF가 authorization code를 토큰으로 교환해 액세스 토큰과 리프레시 토큰을 서버에 보관한다.
브라우저는 OAuth 토큰 대신 애플리케이션 세션으로 BFF를 호출하고,
BFF는 보관한 액세스 토큰으로 downstream Resource Server를 호출한다.
## 근거
- **Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정**
이 결정이 가리키는 구조를 실제로 실행해 본 기록이다.
이 결정대로 만든 구조를 실제로 실행해 본 기록이다.
- **BFF 인증 구조 설계 기준**
이 결정이 PROPOSED인 동안 실제 적용 기준이다.
이 결정이 PROPOSED인 동안 실제로 따르는 기준이다.
- **OAuth/OIDC 인증 패턴 선택 기준**
이 결정을 적용할 조건과 피해야 할 조건이 여기 있다.
이 결정을 적용할 조건과 피해야 할 조건을 갈라 놓은 기록이다.
- **Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출**
access token이 브라우저로 나가 이 요구를 만족하지 못한 경우다.
액세스 토큰이 브라우저로 나가 이 요구를 만족하지 못한 경우다.
## 결정문
브라우저에 OAuth token을 노출하지 않으면서 애플리케이션이 Resource Server 호출을 중계하고 조합해야 하는 경우, BFF가 authorization code 교환과 token 보관, downstream API 호출을 소유한다.
브라우저에 OAuth 토큰을 노출하지 않으면서 애플리케이션이 Resource Server 호출을 중계하고 조합해야 하는 경우, BFF가 authorization code 교환과 토큰 보관, downstream API 호출을 소유한다.
브라우저에는 애플리케이션 session만 제공한다.
브라우저에는 애플리케이션 세션만 제공한다.
## 판단 이유
브라우저에 OAuth token을 전달하지 않으려면 server가 authorization code를 교환하고 access token을 사용해 downstream API를 호출해야 한다.
브라우저에 OAuth 토큰을 전달하지 않으려면 서버가 authorization code를 교환하고 액세스 토큰으로 downstream API를 호출해야 한다.
Mediator 구조에서는 브라우저가 Resource Server를 직접 호출하므로 access token을 /token/access 응답으로 전달한다.
그래서 브라우저에 OAuth token을 제공하지 않는다는 요구에는 맞지 않는다.
Mediator 구조에서는 브라우저가 Resource Server를 직접 호출하다 보니 액세스 토큰을 /token/access 응답으로 내보내고,
그래서 브라우저에 OAuth 토큰을 주지 않는다는 요구에는 맞지 않는다.
Forward-Auth 구조도 브라우저에 OAuth token을 전달하지 않을 수 있지만 upstream은 JWT를 직접 검증하지 않고 edge가 제공한 identity header를 사용한다.
애플리케이션이 access token으로 여러 Resource Server를 직접 호출하거나 사용자별 API 조합을 처리해야 한다면 BFF 쪽이 요구에 더 잘 맞는다.
Forward-Auth 구조도 브라우저에 OAuth 토큰을 전달하지 않을 수 있지만, upstream은 JWT를 직접 검증하지 않고 edge가 넘겨준 identity header를 사용한다.
애플리케이션이 액세스 토큰으로 여러 Resource Server를 직접 호출하거나 사용자별 API 조합을 처리해야 한다면 BFF 쪽이 요구에 더 잘 맞는다.
그래서 이 결정을 적용할지는 브라우저에 OAuth token을 전달하지 않아야 하는지와 함께, 애플리케이션이 downstream API 호출과 조합을 직접 맡아야 하는지까지 보고 정한다.
처리량과 장애 복구 시간, session failover, secret rotation 절차는 확인하지 못해서 이 판단의 근거가 아니다.
## 영향
- BFF가 로그인 상태와 access token, refresh token을 보관하는 보안 구성요소가 된다. 요청을 그대로 넘기는 proxy와 같은 것으로 다루지 않는다.
- 상태 변경 요청마다 CSRF 검증이 필요해진다. 노출되는 값과 제출해야 하는 값이 다를 수 있어서 클라이언트 코드도 그 차이를 알고 있어야 한다.
- 재시작과 replica 이동을 견딜 공유 저장소 저장 token 암호화, 암호화 key 교체를 설계해야 하는데 아직 정하지 않은 문제로 남아 있다.
- logout이 애플리케이션 session과 authorized client를 함께 지워야 하는데, 관리가 달라서 한 번의 삭제로 두 상태가 함께 지워지지 않는다.
- 모든 UI 요청이 BFF를 지나게 되어서 지연과 단일 장애 지점을 준비해야 한다.
- 브라우저에서 token을 없애도 XSS는 여전히 고려해다.
- BFF가 로그인 상태와 액세스 토큰, 리프레시 토큰을 보관하는 보안 구성요소가 되므로, 요청을 그대로 넘기는 프록시처럼 다루지 않는다.
- 상태를 바꾸는 요청마다 CSRF 검증이 붙는다. 클라이언트에 내려가는 값과 실제로 제출해야 하는 값이 다를 수 있어서 클라이언트 코드도 그 차이를 알야 한다.
- 재시작과 replica 이동을 견딜 공유 저장소, 저장한 토큰의 암호화, 암호화 교체를 함께 설계해야 한다. 이 세 가지는 아직 정하지 못했다.
- 로그아웃은 애플리케이션 세션과 authorized client를 함께 지워야 하는데, 두 상태를 따로 보관하다 보니 한 번의 삭제로 둘이 같이 지워지지 않는다.
- 모든 UI 요청이 BFF를 지나므로 지연과 단일 장애 지점을 함께 준비해야 한다.
- 브라우저에서 토큰을 없애도 XSS는 막히지 않는다. 같은 origin에서 실행되는 악성 script는 사용자 세션으로 BFF를 호출할 수 있어서 XSS 방어는 따로 세워다.
@@ -12,62 +12,64 @@ decisionStatus: ADOPTED
decidedOn: 2026-08-24
studio: "https://hyeonworks.com/studio/documents/8c1ebea7-204e-445c-9812-0421d9eb0e9c/edit"
public: "https://hyeonworks.com/projects/keycloak-patterns/decisions/federation-is-not-an-application-pattern"
sourceRevision: keycloak-patterns-lab@2026-08
source:
- final/document.md#선택이-코드와-흐름에-반영되는-방식-google-login
---
# 외부 IdP와의 연동이라도 별도의 인증 방식이 아니다.
Google, Keycloak, 애플리케이션 인증 구조는 각각 역할이 다르다.
Google은 실제 사용자 인증을 수행하는 외부 IDP이고, Keycloak은 Google의 인증 결과를 받아 애플리케이션이 사용할 토큰을 발급한다.
외부 IdP(Identity Provider, 인증 제공자)는 사용자 인증을 실제로 수행하는 쪽이고, Google이 그중 하나다.
Keycloak은 Google의 인증 결과를 받아 애플리케이션이 사용할 토큰을 발급한다.
애플리케이션은 Google을 직접 신뢰하는 것이 아니라 Keycloak이 발급한 토큰을 기준으로 사용자를 인증한다.
SPA, BFF와 같은 구조는 로그인한 사용자의 토큰이나 세션을 어디에 관리할 것인지를 정한다.
따라서 외부 IDP가 붙더라도 인증 구조 바뀌는 것은 아니다.
SPA(Single Page Application)나 BFF(Backend for Frontend) 같은 구조는 로그인한 사용자의 토큰 세션을 어디에 관리할 정한다.
그래서 외부 IdP가 붙어도 애플리케이션의 인증 구조 바뀌지 않는다.
## 근거
- **외부 IdP 연동과 Application 인증 구조의 경계**
이 결정을 규칙으로 편 기준이다.
이 결정을 규칙 문장으로 적어 둔 기준이다.
- **SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계**
브로커가 발급한 code를 받는 애플리케이션 경계다.
브로커가 발급한 Authorization Code를 받는 애플리케이션 경계다.
- **OAuth Token과 Application Session을 구분하는 기준**
upstream IdP 상태와 애플리케이션 상태를 같은 이름으로 부르지 않는다.
upstream IdP 상태와 애플리케이션 상태를 같은 이름으로 부르지 않는다고 갈라 둔 기준이다.
## 결정문
외부 IdP 연동은 별도의 인증 구조가 아니다.
Google과 같은 외부 IDP는 사용자의 인증을 담당하고, Keycloak은 그 인증 결과를 받아 애플리케이션이 신뢰할 수 있는 토큰을 발급한다.
Google과 같은 외부 IdP는 사용자의 인증을 담당한다.
Keycloak은 그 인증 결과를 받아 애플리케이션이 신뢰할 수 있는 토큰을 발급한다.
SPA, Mediator, BFF, OAuth2-proxy와 같은 4가지 구조는 이렇게 발급된 토큰이나 세션을 애플리케이션에서 어디까지 노출하고 관리할지를 구분한다.
## 판단 이유
사용자가 Keycloak 로그인 화면에서 Google 로그인을 선택하면 브라우저는 Google의 Authorization Endpoint로 이동한다.
Google에서 인증이 끝나면 그 결과는 Keycloak으로 돌아오고, Keycloak은 이 응답을 검증해 자신의 사용자 정보와 연결한다.
러고 나서 애플리케이션 callback에는 Keycloak이 발급한 Authorization Code가 전달된다.
사용자가 Keycloak 로그인 화면에서 Google 로그인을 선택하면 브라우저는 Google의 Authorization Endpoint로 이동한다.
Google에서 인증이 끝나면 그 결과는 Keycloak으로 돌아오고, Keycloak은 이 응답을 검증해 자신의 사용자 정보와 연결한다.
다음 애플리케이션 콜백에는 Keycloak이 발급한 Authorization Code가 전달된다.
애플리케이션은 Google과 직접 토큰을 교환하지 않는다.
애플리케이션은 Keycloak이 발급한 Authorization Code를 Keycloak의 Token Endpoint에서 토큰으로 교환한다.
Resource Server가 검증하는 issuer도 Google이 아니라 Keycloak이고, 애플리케이션은 Google token을 받지 않는다.
애플리케이션은 Google과 직접 토큰을 교환하지 않는다.
Keycloak이 발급한 Authorization Code를 Keycloak의 Token Endpoint에서 토큰으로 교환한다.
Resource Server가 검증하는 발급자(issuer)도 Google이 아니라 Keycloak이다.
애플리케이션은 Google이 발급한 토큰을 받지 않는다.
그래서 Google 로그인을 추가해도 애플리케이션 토큰 관리 구조는 달라지지 않는다.
SPA라면 여전히 브라우저에서 토큰을 관리하고, BFF라면 서버가 토큰을 관리하면서 API를 대신 호출해준다.
브라우저가 token을 받는지, 어느 계층이 API를 부르는지도 그대로다.
애플리케이션이 다루는 토큰을 Keycloak이 발급하기 때문에, Google 로그인을 추가해도 애플리케이션 토큰 관리하는 방식은 달라지지 않는다.
SPA라면 여전히 브라우저에서 토큰을 관리하고, BFF라면 서버가 토큰을 관리하면서 API를 대신 호출다.
Google을 구조 하나로 세게 되면 upstream IdP 경계와 애플리케이션 OAuth 경계를 같은 기준으로 묶게 되는데, 두 경계는 검증 방법이 서로 다르다.
두 경계를 섞어 두면 비교표에 성격이 다른 항목이 끼어들고, 계정 연결 규칙도 인증 구조 이야기에 섞여서 따로 설계하지 않고 넘어가게 된다.
래서 외부 IDP 연동과 애플리케이션 인증 구조는 별도의 경계로 나누어 설계하고 검증한다.
Google을 다섯 번째 구조로 세면 upstream IdP 경계와 애플리케이션 OAuth 경계를 같은 기준으로 묶게 되는데, 두 경계는 검증하는 방법이 서로 다르다.
그래서 외부 IdP 연동과 애플리케이션 인증 구조는 별도의 경계로 나누어 설계하고 검증한다.
비교표도 처음에는 다른 모양이었다.
네 구조를 설명하는 용어부터 나란히 놓고 견주었는데, 용어만으로는 어느 계층이 Authorization Code를 교환하고 어느 계층이 API를 부르는지 보이지 않아 로그인과 API 요청을 맡는 구성요소를 같은 표에 놓았다.
## 영향
- Google을 추가하더라도 애플리케이션이 신뢰하고 토큰을 검증하는 대상은 계속 Keycloak이다.
또한 토큰을 브라우저와 서버 중 어디에서 관리하고 어느 계층에서 API를 호출할지는 기존 4가지 구조가 정하는 그대로다.
- 외부 IDP의 계정을 기존 사용자와 어떻게 연결할지는 인증구조와 별개의 문제다.
외부 계정을 식별할 때는 Google과 같은 인증 제공자와 해당 제공자가 부여한 사용자 고유 식별자를 같이 사용한다.
이메일 주소는 변경될 수 있고 서로 다른 인증 제공자에서 같은 이메일을 사용할 수도 있기 때문에 이메일이 같다는 이유로 기존 계정 자동으로 연결하지 않는다.
- 외부 IDP 연동은 테스트 환경에서 확인할 부분과 실제 서비스 환경에서 확인할 부분을 나눠서 검증한다.
Mock Provider를 사용한 테스트에서는 KeyCloak이 외부 IDP의 인증 결과를 정상적으로 받아들이는지,
필요한 사용자 정보가 정상적으로 매핑되는지 확인한다.
실제 Google과 같은 외부 IDP를 연동할 때는 실제 계정으로 로그인이 가능한지, 공개 HTTPS Callback이 정상 작동 하는지, 사용자 동의 과정까지 진행되는지 확인해야 한다.
- Google과 같은 외부 IDP가 늘어나면 KeyCloak에서 관리해야 할 연동 설정도 많아진다.
이 연동 설정을 애플리케이션 팀이 관리할지 별도의 인프라 팀이 관리할지는 아직 정하지 않았다.
실무에서 어느 쪽이 맡는지도 확인하지 않았다.
- Google을 추가도 애플리케이션이 신뢰하고 토큰을 검증하는 대상은 계속 Keycloak이다.
토큰을 브라우저와 서버 중 어디에서 관리하고 어느 계층에서 API를 호출할지는 기존 4가지 구조가 그대로 정한다.
- 외부 IdP의 계정을 기존 사용자와 어떻게 연결할지는 인증 구조와 별개의 문제다.
외부 계정을 식별할 때는 Google과 같은 인증 제공자와 제공자가 부여한 사용자 고유 식별자를 함께 쓴다.
이메일 주소는 바뀔 수 있고 서로 다른 인증 제공자에서 같은 이메일을 수도 있어서, 이메일이 같다는 이유로 기존 계정 자동으로 연결하지 않는다.
- 외부 IdP 연동은 테스트 환경에서 확인할 부분과 실제 서비스 환경에서 확인할 부분을 나눠서 검증한다.
Mock Provider를 붙인 테스트에서는 Keycloak이 외부 IdP의 인증 결과를 제대로 받아들이는지, 필요한 사용자 정보가 제대로 매핑되는지 확인한다.
실제 Google과 같은 외부 IdP를 연동할 때는 실제 계정으로 로그인이 되는지, 공개 HTTPS 콜백이 정상 작동하는지, 사용자 동의 과정까지 진행되는지 확인해야 한다.
- Google과 같은 외부 IdP가 늘어나면 Keycloak에서 관리할 연동 설정도 같이 늘어난다.
이 설정을 애플리케이션 팀이 맡을지 별도의 인프라 팀이 맡을지는 아직 정하지 않았다.