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
+3
-3
@@ -22,8 +22,6 @@ source:
|
||||
|
||||
BFF(Backend For Frontend)는 화면에 필요한 API를 브라우저 대신 호출해 주는 백엔드다. 이 구조에서는 OAuth 토큰을 서버가 보관하고, 브라우저는 토큰 대신 세션 쿠키로 BFF에 요청한다.
|
||||
|
||||
쿠키로 인증하는 요청을 지킬 CSRF 검증, OAuth 토큰을 보관할 인가된 클라이언트(authorized client) 저장소, 로그아웃할 때 세션과 토큰을 함께 지우는 방법, BFF가 호출한 Resource Server에서 오류가 났을 때 이를 브라우저에 무엇으로 바꿔 돌려줄지를 같이 설계해야 한다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정**
|
||||
@@ -37,10 +35,12 @@ BFF(Backend For Frontend)는 화면에 필요한 API를 브라우저 대신 호
|
||||
|
||||
## 목적
|
||||
|
||||
BFF 구조에서는 BFF가 authorization code를 토큰으로 교환하고 그 액세스 토큰으로 Resource Server를 호출하므로, 브라우저의 로그인 상태를 담은 세션과 토큰을 보관하는 인가된 클라이언트를 함께 관리해야 한다.
|
||||
BFF 구조에서는 BFF가 authorization code를 토큰으로 교환하고 그 액세스 토큰으로 Resource Server를 호출한다. 브라우저의 로그인 상태는 세션이 담고 OAuth 토큰은 인가된 클라이언트(authorized client)가 보관하므로, 이 둘을 함께 관리해야 한다. 브라우저 응답에서 토큰이 보이지 않는다고 토큰을 다루는 일까지 없어지지는 않는다. BFF는 요청을 넘겨 주는 프록시가 아니라 로그인 상태와 토큰을 가진 보안 구성요소가 된다.
|
||||
|
||||
쿠키가 자격 증명이 되면 브라우저가 요청마다 자동으로 붙여 보내기 때문에, 값을 바꾸는 요청은 사용자가 의도한 것인지 따로 확인해야 한다. 서버를 재시작하거나 요청이 다른 레플리카로 가더라도 로그인 상태를 이어 갈 저장소도 함께 필요하다.
|
||||
|
||||
쿠키로 인증하는 요청을 지킬 CSRF 검증, OAuth 토큰을 보관할 인가된 클라이언트 저장소, 로그아웃할 때 세션과 토큰을 함께 지우는 방법을 같이 설계해야 한다. BFF가 호출한 Resource Server에서 오류가 났을 때 이를 브라우저에 무엇으로 바꿔 돌려줄지도 함께 정해야 한다.
|
||||
|
||||
## 규칙
|
||||
|
||||
### 1. 브라우저에는 OAuth 토큰을 전달하지 않는다
|
||||
|
||||
+2
-2
@@ -22,8 +22,6 @@ source:
|
||||
|
||||
애플리케이션이 프록시가 붙인 사용자 정보 헤더만 보고 로그인한 사용자를 정하는 구조라면, 그 헤더가 정말 신뢰할 수 있는 프록시에서 온 것인지를 보장해야 한다.
|
||||
|
||||
그러려면 외부 사용자가 애플리케이션에 직접 닿지 못하도록 네트워크 경로를 좁히고, 사용자가 같은 이름의 헤더를 실어 보내도 프록시가 그 값을 지우거나 자기가 확인한 값으로 덮어써야 한다. 프록시를 거친 요청인지 따로 확인해야 하면 내부용 자격 증명도 같이 검증한다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유**
|
||||
@@ -91,6 +89,8 @@ Nginx에서는 해당 location에 internal을 설정해 외부에서 직접 부
|
||||
|
||||
운영 환경에서는 여기에 더 얹어야 한다. 내부 자격 증명 같은 비밀값은 Secret Manager 등으로 안전하게 주입하고 주기적으로 교체할 수 있어야 하고, Network Policy 등으로 모든 요청이 정해진 인증 경로를 지나게 제한해야 한다. 서비스 사이를 더 강하게 묶어야 한다면 mTLS나 Workload Identity를 적용하는 방법도 검토할 수 있다.
|
||||
|
||||
이 네 가지를 확인한 것은 Nginx 설정 하나에서다. 같은 조건이 Traefik ForwardAuth 같은 다른 forward-auth 구현에서도 그대로 성립하는지는 확인하지 못했다. 대안 설정으로 같은 속성을 내려면 전달 헤더를 믿지 않도록 끄는 설정, 인증 응답 헤더를 허용 목록대로만 복사하는 처리, 별도의 로그인 리다이렉트 흐름, 업스트림 내부 자격 증명이나 더 강한 Workload Identity 주입이 함께 필요했다. 살펴본 대안 설정에는 마지막 항목이 없어서 같은 속성을 낸다고 입증하지 못했다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
- 업스트림에 OAuth 클라이언트나 JWT 검증 코드를 넣기 어려울 때
|
||||
|
||||
+1
-3
@@ -18,9 +18,7 @@ source:
|
||||
|
||||
# 외부 IdP 연동과 Application 인증 구조의 경계
|
||||
|
||||
Google 로그인을 붙였다고 해서 다섯 번째 인증 구조가 새로 생기는 것은 아니다. 사용자가 Google에서 인증을 마치면 Keycloak이 그 결과를 받아 사용자를 확인하고, 애플리케이션에는 Keycloak 자신이 발급한 Authorization Code를 전달한다.
|
||||
|
||||
그다음 흐름은 기존과 같다. 애플리케이션은 여전히 Keycloak을 기준으로 인증을 처리하고, 토큰을 브라우저에서 관리할지 서버에서 관리할지에 따라 앞에서 구분한 네 가지 구조 중 하나를 쓴다.
|
||||
Google 로그인을 붙였다고 해서 SPA, Mediator, BFF, OAuth2-Proxy에 이어 다섯 번째 인증 구조가 새로 생기는 것은 아니다. 사용자가 Google에서 인증을 마치면 Keycloak이 그 결과를 받아 사용자를 확인하고, 애플리케이션에는 Keycloak 자신이 발급한 Authorization Code를 전달한다.
|
||||
|
||||
## 관계
|
||||
|
||||
|
||||
-2
@@ -17,8 +17,6 @@ source:
|
||||
|
||||
네 패턴의 경계가 실제로 성립하는지 보려면 스택을 띄우고 브라우저로 로그인까지 해 봐야 하는데, 패턴별 검증 절차는 스택을 다시 만들기 전에 볼륨을 초기화한다. 보존해야 할 realm이나 데이터베이스가 같은 Compose 프로젝트에 있으면 검증을 돌리는 사이에 그 데이터를 잃는다.
|
||||
|
||||
그래서 대상 환경이 일회용인지 확정한 다음에 첫 검사를 돌리고, 마지막에는 테스트용 스택을 내린 뒤 원래 환경을 복구해 다시 확인한다. 중간에 하나라도 어긋나면 나머지를 계속 돌려 전체 통과를 만들지 않는다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유**
|
||||
|
||||
+3
-7
@@ -21,10 +21,6 @@ source:
|
||||
|
||||
IdP의 SSO 세션, 액세스 토큰, 리프레시 토큰, 애플리케이션의 세션 쿠키, 인증 프록시의 세션 쿠키는 만드는 쪽과 쓰는 쪽이 각각 다르고 유효 시간도 서로 다르다.
|
||||
|
||||
액세스 토큰이 만료되었다고 해서 애플리케이션 세션이나 IdP의 SSO 세션까지 같이 만료된 것은 아니고, 애플리케이션 세션을 삭제했다고 해서 IdP의 SSO 세션이나 이미 발급된 토큰이 사라지는 것도 아니다.
|
||||
|
||||
그래서 어떤 세션과 토큰이 아직 살아 있는지, 무엇이 만료되었는지, 로그아웃할 때 어떤 상태를 삭제하거나 무효화해야 하는지를 종류마다 나눠서 확인한다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계**
|
||||
@@ -38,9 +34,9 @@ IdP의 SSO 세션, 액세스 토큰, 리프레시 토큰, 애플리케이션의
|
||||
|
||||
## 목적
|
||||
|
||||
SPA(Single Page Application, 단일 페이지 애플리케이션), Mediator, BFF(Backend For Frontend) 구성에서는 Resource Server가 액세스 토큰을 검증한 뒤 JWT의 클레임에서 사용자 정보를 얻을 수 있다. Forward-Auth 구성에서는 애플리케이션이 인증 프록시가 전달한 사용자 정보 헤더를 쓴다. 두 경로가 같은 사용자 이름을 내놓더라도 한쪽은 액세스 토큰을 검증해서 얻은 값이고, 다른 한쪽은 신뢰하기로 정한 프록시가 전달한 값이다.
|
||||
SPA(Single Page Application, 단일 페이지 애플리케이션), Mediator, BFF(Backend For Frontend) 구성에서는 Resource Server가 액세스 토큰을 검증한 뒤 JWT의 클레임에서 사용자 정보를 얻을 수 있다. Forward-Auth 구성에서는 애플리케이션이 인증 프록시가 전달한 사용자 정보 헤더를 쓴다. 두 경로가 같은 사용자 이름을 내놓더라도 한쪽은 액세스 토큰을 검증해서 얻은 값이고, 다른 한쪽은 신뢰하기로 정한 프록시가 전달한 값이다. 예를 들어 액세스 토큰의 `preferred_username`과 프록시가 만든 `X-Auth-Request-User`에는 모두 `regular-user`가 들어갈 수 있고, 이름만으로는 어느 쪽을 검증해서 얻은 값인지 구분되지 않는다.
|
||||
|
||||
로그아웃과 만료 처리에서도 같은 구분이 필요하다. IdP의 SSO 세션, 액세스 토큰과 리프레시 토큰, 애플리케이션 세션은 서로 다른 주체가 관리하고 수명도 다르다. 그래서 로그아웃할 때 무엇을 삭제하거나 무효화할지, 어떤 자격 증명이 만료되었을 때 어떤 상태를 계속 쓸 수 있는지를 각각 나눠서 설계한다.
|
||||
로그아웃과 만료 처리에서도 같은 구분이 필요하다. IdP의 SSO 세션, 액세스 토큰과 리프레시 토큰, 애플리케이션 세션은 서로 다른 주체가 관리하고 수명도 다르다. 액세스 토큰이 만료되었다고 해서 애플리케이션 세션이나 IdP의 SSO 세션까지 같이 만료된 것은 아니다. 그래서 로그아웃할 때 무엇을 삭제하거나 무효화할지, 어떤 자격 증명이 만료되었을 때 어떤 상태를 계속 쓸 수 있는지를 각각 나눠서 설계한다.
|
||||
|
||||
## 규칙
|
||||
|
||||
@@ -72,7 +68,7 @@ IdP SSO 세션, OAuth 액세스 토큰, OAuth 리프레시 토큰, 애플리케
|
||||
|
||||
OAuth 토큰을 JavaScript 메모리에만 두면 Local Storage나 Session Storage 같은 Web Storage에 토큰을 계속 저장하지 않을 수 있다. 다만 실행 중인 브라우저 JavaScript에서 토큰에 접근할 수 없다는 뜻은 아니다.
|
||||
|
||||
애플리케이션이 Token Endpoint의 응답을 JavaScript로 받아 처리한다면 실행 중에는 토큰 값이 JavaScript가 다루는 메모리에 있다. 같은 Origin에서 악성 스크립트가 실행될 수 있는 상황이라면 토큰 응답이나 애플리케이션이 토큰을 처리하는 경로가 공격 대상이 될 수 있으므로, Web Storage에 토큰을 저장하지 않는 것만으로는 XSS(Cross-Site Scripting, 사이트 간 스크립팅)로 토큰에 접근하는 것까지 막지 못한다.
|
||||
애플리케이션이 Token Endpoint의 응답을 JavaScript로 받아 처리한다면 실행 중에는 토큰 값이 JavaScript가 다루는 메모리에 있다. 같은 Origin에서 악성 스크립트가 실행될 수 있는 상황이라면 토큰 응답이나 애플리케이션이 토큰을 처리하는 경로가 공격 대상이 될 수 있다. 그래서 Web Storage에 토큰을 저장하지 않는 것만으로는 XSS(Cross-Site Scripting, 사이트 간 스크립팅)로 토큰에 접근하는 것까지 막지 못한다.
|
||||
|
||||
### 6. 로그아웃 범위를 상태별로 적는다
|
||||
|
||||
|
||||
Reference in New Issue
Block a user