96 lines
6.4 KiB
Markdown
96 lines
6.4 KiB
Markdown
---
|
|
id: 97eddd97-1096-426a-a2c6-a6c5bf1cd09f
|
|
kind: REFERENCE
|
|
slug: bff-authentication-design-criteria
|
|
title: BFF 인증 구조 설계 기준
|
|
topic: OAuth/OIDC 인증 경계
|
|
project: KeyCloak Patterns
|
|
status: 게시 전
|
|
version: 10
|
|
studio: "https://hyeonworks.com/studio/documents/97eddd97-1096-426a-a2c6-a6c5bf1cd09f/edit"
|
|
---
|
|
|
|
# BFF 인증 구조 설계 기준
|
|
|
|
BFF가 OAuth token을 server-side에서 관리하고 브라우저는 session cookie로 BFF를 호출할 때 필요한 설계 항목을 정리한다. CSRF 검증, authorized client 저장소, logout, downstream 오류 처리가 핵심이다.
|
|
|
|
## 관계
|
|
|
|
- **BFF에서 OAuth Token을 관리할 때 Session과 CSRF를 처리한 과정**
|
|
이 기준의 항목 중 실제로 구현된 것과 비어 있는 것을 센 기록이다.
|
|
- **서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가**
|
|
저장소 항목이 아직 답이 없는 질문으로 남아 있다.
|
|
- **BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가**
|
|
어느 저장소에 둘지가 이 기준의 미결 항목이다.
|
|
- **BFF가 OAuth Token을 관리하는 조건**
|
|
이 결정이 PROPOSED인 동안 실제 적용 기준은 이 문서다.
|
|
|
|
## 목적
|
|
|
|
BFF 구조에서는 BFF가 authorization code를 token으로 교환하고 access token을 사용해 Resource Server를 호출한다. 따라서 session과 authorized client를 함께 관리하는 보안 구성요소로 본다.
|
|
|
|
cookie가 credential이 되면 브라우저가 요청마다 자동으로 붙인다. 값을 바꾸는 요청은 사용자의 의도인지 따로 확인해야 한다. 그리고 재시작과 replica 이동을 견딜 저장소도 함께 필요해진다.
|
|
|
|
여기 있는 것은 「BFF를 쓴다」로 답이 되지 않는 항목들이다.
|
|
|
|
## 규칙
|
|
|
|
### 1. 브라우저에는 OAuth token을 전달하지 않는다
|
|
|
|
access token과 refresh token은 server-side authorized client에 보관한다. 브라우저가 token을 직접 사용할 필요가 없도록 BFF가 downstream 요청의 `Authorization` 헤더를 만든다.
|
|
|
|
session cookie는 downstream으로 전달하지 않는다. BFF가 session을 애플리케이션 credential로 소비하고, Resource Server가 아는 Bearer 요청을 새로 만든다. 두 credential은 같은 요청 처리 안에 있지만 검증하는 주체가 다르다.
|
|
|
|
### 2. cookie가 credential이면 상태 변경 요청에 CSRF 검증을 둔다
|
|
|
|
session cookie는 브라우저가 자동으로 전송하므로 상태 변경 endpoint에는 CSRF 검증을 적용한다. 현재 구성은 JavaScript가 CSRF cookie를 읽어 요청 헤더에 같은 값을 전달하는 방식을 사용한다.
|
|
|
|
노출 값과 제출 값이 다를 수 있다. 응답 본문의 token이 가려진 값이면 헤더에 넣는 값은 cookie에서 읽어야 한다. 두 값을 같다고 가정하고 구현하면 클라이언트가 그대로 403을 받는다.
|
|
|
|
SameSite와 CSRF token은 역할이 다르다. SameSite는 특정 cross-site 요청에서 cookie 전송을 제한하는 브라우저 정책이고, CSRF token은 cookie가 포함된 상태 변경 요청을 서버가 추가로 검증하는 값이다. 같은 site로 계산되는 다른 origin 요청도 고려해야 한다.
|
|
|
|
### 3. session과 authorized client의 수명주기를 따로 설계한다
|
|
|
|
session은 session ID로 조회하고 authorized client는 registration 이름과 principal name으로 조회한다. shared store를 도입할 때 두 저장 구조를 각각 확인해야 한다.
|
|
|
|
같은 사용자가 두 브라우저에서 로그인하면 같은 token 항목을 공유하거나 덮어쓴다. session ID마다 token을 따로 보관해야 하면 그렇게 설계해야 한다.
|
|
|
|
저장소는 재시작과 replica 이동을 견뎌야 한다. 공유 durable store와 session affinity, 저장 token 암호화 중 무엇을 쓸지 정하고 암호화 key 교체 방법도 같이 정한다.
|
|
|
|
logout에서는 application session과 authorized client를 모두 정리한다. 두 상태의 lookup key가 다르므로 삭제 처리도 각각 확인해야 한다.
|
|
|
|
### 4. downstream 오류를 화면 오류로 바꾸는 규칙을 둔다
|
|
|
|
Resource Server의 401을 그대로 내려보내면 사용자는 로그인이 끊긴 것인지 권한이 없는 것인지 알 수 없다. timeout과 retry, circuit breaker, 재로그인 전환도 함께 정한다. 모든 UI 요청이 BFF를 지나기 때문에 여기서 정하지 않으면 화면마다 다르게 처리된다.
|
|
|
|
### 5. 자기 보고 값을 증거로 쓰지 않는다
|
|
|
|
「브라우저에 token이 없다」고 서버가 응답에 적는 값은 서버가 넣은 상수다. 브라우저를 들여다본 결과가 아니다.
|
|
|
|
진단 endpoint의 응답과 별개로 브라우저 개발자 도구에서 network 요청과 Web Storage를 직접 확인한다. 애플리케이션이 스스로 보고한 값과 브라우저에서 관측한 결과를 구분해 기록한다.
|
|
|
|
### 6. BFF에서도 XSS 방어는 별도로 필요하다
|
|
|
|
same-origin에서 악성 script가 실행되면 피해자 session으로 BFF endpoint를 호출하고 JavaScript에서 읽을 수 있는 CSRF cookie에도 접근할 수 있다. BFF는 OAuth token 원문을 브라우저 JavaScript에 전달하지 않지만, CSP와 output encoding, 의존성 무결성, 애플리케이션 인가는 별도로 적용해야 한다.
|
|
|
|
## 적용 조건
|
|
|
|
- 브라우저가 OAuth token을 받아서는 안 될 때
|
|
- backend가 화면에 맞춰 여러 API를 조합해야 할 때
|
|
- 로그인 상태를 애플리케이션이 소유해야 할 때
|
|
- downstream API가 늘어나도 브라우저는 하나만 알게 하고 싶을 때
|
|
|
|
## 예외
|
|
|
|
- stateless 직접 API 호출과 독립 client가 핵심이면 BFF를 넣지 않는다. server state와 단일 장애 지점만 늘어난다.
|
|
- 브라우저의 직접 API 호출을 남겨야 하면 refresh credential만 서버로 분리하는 구조가 맞다.
|
|
- server state를 둘 수 없는 환경이면 브라우저가 token을 직접 다루는 구조가 더 단순하다.
|
|
|
|
## 예시
|
|
|
|
- 브라우저 요청에는 Authorization 헤더가 없고 session cookie만 있다
|
|
- BFF가 authorized client에서 access token을 읽어 downstream Bearer 요청을 새로 만든다
|
|
- CSRF 헤더가 없는 POST는 403이 되고 cookie의 raw 값을 헤더에 넣은 POST는 200이 된다
|
|
- 응답 본문의 token은 가려진 값이고 헤더에 넣는 값은 cookie의 raw 값이다
|
|
- 진단 endpoint의 browserTokenCount는 controller literal이라서 token 비노출의 근거가 아니다
|