chore: 이전 세션이 남긴 변경을 커밋한다
이번 파이프라인 작업과 무관하게 작업 트리에 남아 있던 것을 그대로 올린다. 사용자가 「전부 커밋」으로 정했고, 이번 작업과 섞이지 않게 커밋만 나눴다. 대부분은 clean-architecture-backend-template 의 그림 정본 재배치다 — final/assets/diagrams/<이름>/ 에 있던 것이 CLAUDE.md 가 적은 배치인 final/assets/<이름>/ 로 옮겨졌고 .techviz/<이름>/ 이 함께 들어왔다. 삽입 줄의 대부분(3.15M)이 그 .techviz context.json 이다. 그 밖에 ca-tmpl·document-haness 의 정리, .claude/agents/ 열한 개, writing-practitioner-guides 스킬, .playwright-mcp 세션 산출물, scripts/check-ssot-facts.py 와 그 시험이 들어 있다. 이 커밋의 내용은 내가 만든 것이 아니라 이전 세션이 남긴 것이고 검증하지 않았다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2109f726fe
commit
ab59130196
+11
-15
@@ -20,20 +20,7 @@ source:
|
||||
|
||||
# BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가
|
||||
|
||||
현재 BFF의 세션과 Authorized Client는 프로세스 메모리에 저장된다. 재시작과 레플리카 이동 뒤에도 로그인 상태를 유지하려면 두 상태를 어디에 저장할지 정해야 한다.
|
||||
|
||||
BFF(Backend for Frontend)가 서버에 들고 있는 상태는 둘이다. 하나는 브라우저의 로그인 세션이고,
|
||||
다른 하나는 Keycloak에서 받은 액세스 토큰과 리프레시 토큰을 담아 두는 Authorized Client다.
|
||||
세션은 `session ID`로 찾지만 Authorized Client는 `client registration 이름`과 `principal name`으로 찾는다.
|
||||
|
||||
찾는 열쇠가 이렇게 다르니 두 상태를 반드시 한 저장소에 담아야 하는 것은 아니다.
|
||||
각각의 조회 방식과 운영 요구사항에 맞게 저장 구조를 따로 정할 수 있다.
|
||||
|
||||
공유 저장소 후보로 Redis를 우선 보고 있지만 아직 고르지 않았다.
|
||||
액세스 토큰과 리프레시 토큰을 Redis에 담는다면 토큰을 어떤 방식으로 암호화할지, 세션과 토큰의 만료 시간을 어떻게 맞출지,
|
||||
로그아웃할 때 세션과 Authorized Client가 모두 지워지는지를 확인해야 하는데 아직 확인하지 않았다.
|
||||
|
||||
토큰 보호와 만료 처리, 로그아웃 뒤 상태 정리까지 검증한 다음에 저장 구조를 정한다.
|
||||
현재 BFF(Backend for Frontend)의 세션과 Authorized Client는 프로세스 메모리에 저장된다. 재시작과 레플리카 이동 뒤에도 로그인 상태를 유지하려면 두 상태를 어디에 저장할지 정해야 한다.
|
||||
|
||||
## 관계
|
||||
|
||||
@@ -48,13 +35,19 @@ BFF(Backend for Frontend)가 서버에 들고 있는 상태는 둘이다. 하나
|
||||
|
||||
## 사실
|
||||
|
||||
- BFF가 서버에 들고 있는 상태는 둘이다.
|
||||
하나는 브라우저의 로그인 세션이고, 다른 하나는 Keycloak에서 받은 액세스 토큰과 리프레시 토큰을 담아 두는 Authorized Client다.
|
||||
JavaScript 응답에 OAuth 토큰이 보이지 않았을 때는 토큰을 다루는 일도 같이 사라진 것처럼 보였지만,
|
||||
BFF 코드를 따라가 보니 BFF가 세션에서 Authorized Client를 찾아 액세스 토큰을 붙여 내부 API를 대신 불렀다.
|
||||
- 현재 구성에는 Spring Session도, Redis도, JDBC 저장소도, 토큰을 암호화해 담는 저장소도 없다.
|
||||
- HttpSession은 서블릿 컨테이너의 메모리 구현을 쓰기 때문에, 그 프로세스가 종료되면 세션 데이터도 같이 사라진다.
|
||||
- OAuth2AuthorizedClientService도 Spring Boot 자동구성이 고르는 메모리 구현이다.
|
||||
- 세션은 session ID로 조회하고 Authorized Client는 registration 이름과 principal name으로 조회한다.
|
||||
- 세션은 session ID로 조회하지만 Authorized Client는 registration 이름과 principal name으로 조회한다.
|
||||
두 저장 구조를 공유 저장소로 옮길 때는 각각 따로 설계해야 한다.
|
||||
- Authorized Client 매니저에는 authorization-code와 refresh-token provider가 함께 구성돼 있어서,
|
||||
저장소를 공유하면 여러 인스턴스가 같은 항목을 동시에 갱신할 수 있다.
|
||||
- 여기까지는 한 대에서 실행한 학습 환경에서 코드와 테스트로 확인한 것이다.
|
||||
여러 인스턴스가 세션과 Authorized Client를 공유하는지는 실행해 확인하지 못했다.
|
||||
|
||||
## 가정
|
||||
|
||||
@@ -78,6 +71,7 @@ BFF(Backend for Frontend)가 서버에 들고 있는 상태는 둘이다. 하나
|
||||
- Authorized Client를 찾을 때는 session ID를 쓰지 않아서, 세션만 공유해도 같은 사용자의 여러 세션이 같은 토큰을 본다.
|
||||
- 현재 테스트에는 저장소 계약이 없다.
|
||||
- 모든 화면 요청이 BFF를 지나기 때문에 저장소가 느려지면 화면도 바로 느려진다.
|
||||
다만 이 학습 환경에서는 처리량을 재지 못해서, 어느 지점부터 느려지는지는 숫자로 말할 수 없다.
|
||||
|
||||
## 선택지
|
||||
|
||||
@@ -137,4 +131,6 @@ Redis와 관계형 DB를 둘 다 인증 경로에서 쓰게 되므로 모니터
|
||||
4. 로그아웃 뒤 두 저장소에 잔여 항목이 없는지 확인한다.
|
||||
5. 저장소를 끊은 상태에서 로그인과 API 호출이 어떤 오류를 내는지 본다.
|
||||
|
||||
토큰 보호와 만료 처리, 로그아웃 뒤 상태 정리까지 검증한 다음에 저장 구조를 정한다.
|
||||
|
||||
암호화 키 교체 절차는 후보를 고른 뒤에 따로 설계한다.
|
||||
|
||||
+3
-5
@@ -21,10 +21,6 @@ source:
|
||||
|
||||
지금 엣지는 인증된 사용자의 `user`와 `email`만 헤더로 넘기고, 업스트림 애플리케이션은 역할(role)을 보고 인가를 판단하지 않는다.
|
||||
|
||||
그래서 역할 기반 인가가 필요한 요구가 들어왔을 때 어떻게 처리할지 아직 정해 두지 않았다. 엣지가 사용자의 역할까지 확인해 헤더로 넘길지, 애플리케이션이 역할과 권한을 직접 조회해 인가를 판단할지를 정해야 한다.
|
||||
|
||||
역할이나 권한처럼 애플리케이션 기능에 가까운 값이 계속 늘어난다면, 그 값을 엣지 헤더에 계속 더하는 대신 인가를 애플리케이션이 맡는 구조가 나은지도 함께 검토한다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유**
|
||||
@@ -49,8 +45,10 @@ source:
|
||||
|
||||
## 미지수
|
||||
|
||||
- 역할 기반 인가가 필요한 요구가 들어왔을 때 어떻게 처리할지. 엣지가 사용자의 역할까지 확인해 헤더로 넘길지, 애플리케이션이 역할과 권한을 직접 조회해 인가를 판단할지.
|
||||
- 역할이 여러 개일 때 어떤 구분자와 이스케이프 규칙으로 보낼지. 값 안에 그 구분자가 들어오면 어떻게 되는지.
|
||||
- 헤더 크기 상한을 넘으면 어떻게 되는지. 프록시가 잘라 내는지, 요청 자체가 거부되는지.
|
||||
현재 fixture는 `/api/edge`와 `/`를 모두 `/edge/me`로 바꿔서 큰 헤더가 실린 요청을 통과시켜 본 적이 없다.
|
||||
- 역할이 바뀌었을 때 프록시 세션과 다운스트림 인가에 언제 반영되는지. 권한을 바꾸고 몇 분 뒤에 반영되는지.
|
||||
- 업스트림이 헤더가 있는지만 볼지, 값과 내부 서비스 식별값(service identity)까지 함께 볼지.
|
||||
|
||||
@@ -95,7 +93,7 @@ source:
|
||||
|
||||
이러면 엣지는 애플리케이션의 역할과 테넌트, 권한 정책을 알 필요가 없다. BFF가 사용자와 권한을 조회해 인가를 판단하고, 화면에 필요한 여러 Resource Server의 API를 불러 결과를 합칠 수 있다.
|
||||
|
||||
대신 BFF를 넣으면 서버가 다시 인증 상태를 들고 있어야 한다. 브라우저와 BFF 사이의 애플리케이션 세션을 보호해야 하고, 쿠키 기반 세션을 쓴다면 쿠키가 요청마다 자동으로 붙어서 값을 바꾸는 요청에는 사용자가 의도한 것인지 확인하는 CSRF(Cross-Site Request Forgery) 검증도 있어야 한다. BFF를 여러 대로 늘려 인증 상태를 유지하려면 세션과 authorized client를 어떻게 공유할지 정하고, 공유 저장소의 장애와 만료 처리까지 운영해야 한다.
|
||||
대신 BFF를 넣으면 서버가 다시 인증 상태를 들고 있어야 한다. 브라우저와 BFF 사이의 애플리케이션 세션을 보호해야 한다. 쿠키 기반 세션을 쓴다면 쿠키가 요청마다 자동으로 붙으니, 값을 바꾸는 요청에는 사용자가 의도한 것인지 확인하는 CSRF(Cross-Site Request Forgery) 검증도 있어야 한다. BFF를 여러 대로 늘려 인증 상태를 유지하려면 세션과 authorized client를 어떻게 공유할지 정하고, 공유 저장소의 장애와 만료 처리까지 운영해야 한다.
|
||||
|
||||
이 구조를 골랐다가 다시 엣지 쪽으로 되돌린다면 BFF가 맡던 사용자별 인가를 업스트림이나 별도 정책 서비스로 다시 옮겨야 한다.
|
||||
|
||||
|
||||
+4
-6
@@ -21,8 +21,6 @@ source:
|
||||
|
||||
Mediator와 BFF(Backend for Frontend)는 브라우저의 로그인 세션과 OAuth 토큰을 서로 다른 저장소에 둔다. 지금 구현은 두 저장소를 모두 애플리케이션 서버의 메모리에 두기 때문에, 서버 프로세스가 끝나면 담아 둔 상태도 같이 사라진다.
|
||||
|
||||
운영에서 서버를 여러 인스턴스로 띄우려면 여기에 설계를 더해야 한다. 사용자의 요청이 로그인할 때와 다른 인스턴스로 가도 세션과 토큰을 찾을 수 있어야 하고, 서버를 재시작한 뒤에도 로그인 상태를 이어 갈지 정해야 한다. 로그아웃할 때 여러 인스턴스에 흩어져 있는 세션과 토큰을 어떻게 함께 지울지도 정해야 한다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정**
|
||||
@@ -55,15 +53,15 @@ Mediator와 BFF(Backend for Frontend)는 브라우저의 로그인 세션과 OAu
|
||||
- 요청이 다른 인스턴스로 가도 같은 세션을 찾는가.
|
||||
- 같은 사용자의 여러 세션이 Authorized Client 항목 하나를 함께 쓰는가, 아니면 서로 덮어쓰는가. 한쪽에서 로그아웃하면 다른 쪽 로그인도 끊기는가.
|
||||
- 저장한 Refresh Token이 암호화되는가. 저장소를 여는 사람이 그 값을 그대로 읽는가.
|
||||
- 로그아웃할 때 HttpSession과 Authorized Client를 모두 지우는가. 한쪽만 지웠을 때 다음 요청이나 재로그인에서 어떤 상태가 살아나는가.
|
||||
- 로그아웃할 때 HttpSession과 Authorized Client를 모두 지우는가. 한쪽만 지웠을 때 다음 요청이나 재로그인에서 어떤 상태가 살아나는가. 여러 인스턴스에 흩어져 있는 세션과 토큰은 어떻게 함께 지우는가.
|
||||
- 세션 만료와 토큰 만료가 어긋나면 무엇이 먼저 실패하고 사용자 화면에는 무엇이 보이는가.
|
||||
- OAuth2-Proxy 구조의 레플리카들은 같은 Cookie Secret을 어떻게 나눠 갖고 어떻게 바꾸는가. 바꾸는 동안 이미 로그인해 있던 사람은 어떻게 되는가.
|
||||
|
||||
## 제약
|
||||
|
||||
- 지금은 인스턴스 하나로만 실행하고 있어서 레플리카 사이의 세션 조회나 장애 조치(failover) 동작은 아직 만들지 않았다.
|
||||
- 지금은 인스턴스 하나로만 실행하는 학습 환경이어서 레플리카 사이의 세션 조회나 장애 조치(failover) 동작은 아직 만들지 않았다. 장애 복구에 걸리는 시간과 비밀값 교체 절차도 확인한 범위에 넣지 못했다.
|
||||
- Authorized Client는 세션 ID로 저장하지도 조회하지도 않는다. 그래서 여러 인스턴스가 같은 세션을 쓰도록 세션 저장소만 공유 저장소로 바꿔서는 부족하다. 로그인 세션을 여러 인스턴스가 나눠 쓰는 방법과, OAuth 토큰이 들어 있는 Authorized Client를 어디에 어떻게 저장할지는 각각 따로 설계해야 한다.
|
||||
- Resource Server의 8081이 host에도 열려 있어서, 모든 클라이언트가 BFF만 거치도록 네트워크가 강제하고 있지는 않다.
|
||||
- Resource Server의 8081이 호스트에도 열려 있어서, 모든 클라이언트가 BFF만 거치도록 네트워크가 강제하고 있지는 않다.
|
||||
|
||||
## 선택지
|
||||
|
||||
@@ -79,7 +77,7 @@ Sticky Session은 같은 세션에서 온 요청을 되도록 같은 애플리
|
||||
|
||||
다만 그 인스턴스가 종료되면 그 메모리에 있던 로그인 세션과 OAuth 토큰도 함께 사라진다. 배포나 오토스케일링으로 인스턴스가 자주 바뀌는 환경이라면 Sticky Session만으로 로그인 상태를 지키기 어렵고, 인스턴스가 사라졌을 때 상태를 어떻게 되살릴지 따로 설계해야 한다.
|
||||
|
||||
### 3. 브라우저가 token을 들고 API를 직접 부른다
|
||||
### 3. 브라우저가 토큰을 들고 API를 직접 부른다
|
||||
|
||||
서버에 로그인 세션이나 OAuth 토큰을 두지 않는 구조로 바꾸면 여러 인스턴스가 나눠 가질 상태 자체가 없어서, 공유 저장소도 session affinity도 필요하지 않다. Resource Server는 요청마다 실려 온 Access Token을 검증해서 처리한다.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user