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:
co-authored by
Claude Fable 5.1
parent
62520a4dce
commit
4d50bb939a
+26
-22
@@ -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 방어는 따로 세워야 한다.
|
||||
|
||||
+35
-33
@@ -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에서 관리할 연동 설정도 같이 늘어난다.
|
||||
이 설정을 애플리케이션 팀이 맡을지 별도의 인프라 팀이 맡을지는 아직 정하지 않았다.
|
||||
|
||||
Reference in New Issue
Block a user