feat: 문서 구조 변경 및 tech-visual 스킬 추가
This commit is contained in:
+131
@@ -0,0 +1,131 @@
|
||||
---
|
||||
id: c72656b5-842d-45d9-b5f6-82b66b09d0b9
|
||||
kind: QUESTION
|
||||
slug: server-session-pattern-multi-instance
|
||||
title: 서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가
|
||||
topic: OAuth/OIDC 인증 경계
|
||||
project: KeyCloak Patterns
|
||||
status: 게시 중
|
||||
version: 39
|
||||
questionStatus: OPEN
|
||||
studio: "https://hyeonworks.com/studio/documents/c72656b5-842d-45d9-b5f6-82b66b09d0b9/edit"
|
||||
public: "https://hyeonworks.com/questions/server-session-pattern-multi-instance"
|
||||
---
|
||||
|
||||
# 서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가
|
||||
|
||||
Mediator와 BFF는 브라우저의 로그인 세션과 OAuth token을 서로 다른 저장소에서 관리한다.
|
||||
현재 구현에서는 두 저장소 모두 애플리케이션 서버의 메모리에 있기 때문에, 서버 프로세스가 종료되면 저장된 상태도 같이 사라진다.
|
||||
|
||||
따라서 운영 환경에서 서버를 여러 인스턴스로 구성하게 될 경우 추가 설계가 필요한데, 사용자의 요청이 로그인할 때와 다른 인스턴스로 전달되어도 세션과 토큰을 찾을 수 있어야 하고, 서버가 재시작된 뒤에도 로그인 상태를 유지할 것인지 결정해야 한다.
|
||||
또한 로그아웃할 때 여러 인스턴스에 걸쳐 저장된 세션과 토큰을 어떻게 같이 제거할지도 정해야 한다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정**
|
||||
두 상태가 모두 process-local memory에 있다는 사실의 출처다.
|
||||
- **Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출**
|
||||
같은 저장소 구성을 쓰는 다른 패턴이다.
|
||||
- **BFF 인증 구조 설계 기준**
|
||||
이 질문의 답이 이 기준의 빈 항목을 채운다.
|
||||
- **BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가**
|
||||
저장소 후보 비교로 독립시킨 질문이다.
|
||||
|
||||
## 사실
|
||||
|
||||
- Mediator와 BFF는 로그인 상태와 OAuth Token을 서로 다른 저장소에서 관리한다.
|
||||
로그인 상태는 HttpSession에 저장하고 session ID로 조회한다.
|
||||
반면 OAuth Token은 OAuth2AuthorizedClientService에 저장하며, client registration 이름과 principal name을 기준으로 조회한다.
|
||||
- 현재 세션과 OAuth Token을 저장할 별도의 저장소를 직접 설정하지 않았다.
|
||||
따라서 Spring Boot의 자동 구성이 고르는 메모리 기반 기본 구현이 사용된다.
|
||||
|
||||
다만 코드에 저장소를 직접 생성하는 Bean이 없기 때문에, 어떤 구현체가 실제로 사용되는지는 Spring Boot의 자동 구성 결과까지 확인해야 정확하게 알 수 있다.
|
||||
- 현재는 Spring Session이나 Redis, JDBC 기반 Token Store와 같은 외부 저장소를 사용하지 않는다.
|
||||
따라서 로그인 세션과 OAuth Token 정보는 모두 해당 애플리케이션 인스턴스의 메모리에 저장된다.
|
||||
|
||||
이 때문에 인스턴스가 종료되거나 재시작되면 해당 인스턴스가 가지고 있던 로그인 세션과 OAuth Token 정보도 같이 없어진다.
|
||||
- Authorized Client는 세션별로 구분되지 않는다.
|
||||
조회 기준에 session ID가 없기 때문에 같은 사용자가 여러 브라우저에서 로그인하면 동일한 Authorized Client 정보를 사용하게 된다.
|
||||
- OAuth2-Proxy 구조에서는 로그인 상태를 별도의 서버 저장소에 보관하지 않고,
|
||||
필요한 최소한의 정보를 브라우저의 세션 쿠키에 담아 관리한다.
|
||||
현재 설정에서는 이 쿠키의 유효 시간을 1시간으로 두고 있다.
|
||||
- 현재 테스트에는 서버가 재시작되거나 사용자의 요청이 다른 인스턴스로 전달된 뒤에도 로그인 상태와 OAuth Token을 정상적으로 사용할 수 있는지 확인하는 항목이 없다.
|
||||
|
||||
따라서 세션이나 Token 저장 방식을 변경하더라도 기존 동작이 그대로 유지되는지는 별도로 확인 및 검증이 필요하다.
|
||||
|
||||
## 가정
|
||||
|
||||
- 운영에서는 인스턴스가 둘 이상이다.
|
||||
- 재시작과 배포가 로그인 상태를 끊어서는 안 되는데, 지금 구조에서는 끊기게 된다.
|
||||
- 같은 사용자의 여러 브라우저 session이 서로의 token 항목을 덮어써서는 안 된다.
|
||||
|
||||
## 미지수
|
||||
|
||||
- 재시작 뒤 로그인이 유지되는가. 지금은 안 된다는 것까지 알지만 무엇을 바꿔야 되는지는 정하지 않았다.
|
||||
- 인스턴스가 바뀌어도 같은 session을 찾게 되는가.
|
||||
- 같은 사용자의 여러 session이 authorized client 항목을 공유하거나 덮어쓰게 되는가.
|
||||
한쪽에서 로그아웃하면 다른 쪽도 끊기게 되는가.
|
||||
- 저장된 refresh token이 암호화되는가.
|
||||
저장소를 여는 사람이 그 값을 그대로 읽게 되는가.
|
||||
- logout에서 HttpSession과 authorized client를 모두 정리하는가.
|
||||
한쪽만 삭제했을 때 다음 요청이나 재로그인에서 어떤 상태가 복원되는가.
|
||||
- session 만료와 token 만료가 어긋나면 무엇이 먼저 실패하고 사용자 화면에는 어떻게 보이게 되는가.
|
||||
- OAuth2-Proxy 구조의 replica들이 같은 cookie secret을 어떻게 공유하고 교체하게 되는가.
|
||||
교체하는 동안 로그인해 있던 사람은 어떻게 되는가.
|
||||
|
||||
## 제약
|
||||
|
||||
- 현재 구조는 단일 인스턴스로 실행하고 있어 replica 간 session 조회와 failover 동작은 아직 구현되지 않았다.
|
||||
- Authorized Client는 session ID를 기준으로 저장하거나 조회하지 않는다.
|
||||
따라서 여러 인스턴스가 같은 세션을 사용할 수 있도록 Session Store를 공유 저장소로 변경하는 것만으로는 충분하지 않다.
|
||||
|
||||
로그인 세션을 여러 인스턴스에서 공유하는 방법과 OAuth Token이 저장된 Authorized Client를 어떻게 저장하고 공유할지는 각각 별도로 설계해야 한다.
|
||||
- Resource Server의 8081이 host에도 열려 있어서 모든 client가 BFF만 거치도록 network에서 강제된 상태가 아니다.
|
||||
|
||||
## 선택지
|
||||
|
||||
### 1. 공유 저장소를 사용한다
|
||||
|
||||
HttpSession과 Authorized Client를 모두 외부의 공유 저장소에 보관하면 여러 애플리케이션 인스턴스가 동일한 로그인 세션과 OAuth Token 정보를 조회할 수 있다.
|
||||
따라서 사용자의 요청이 다른 인스턴스로 전달되거나 특정 인스턴스가 재시작되더라도 기존 로그인 상태를 계속 사용할 수 있다.
|
||||
|
||||
다만 인증 과정이 외부 저장소에 의존하게 되므로 추가로 고려해야 할 사항이 생긴다.
|
||||
저장소에 장애가 발생했을 때 인증 요청을 어떻게 처리할지 정해야 하고, 세션과 Token을 어떤 형식으로 저장할지와 저장된 Token을 어떻게 보호할지도 결정해야 한다.
|
||||
또한 세션은 남아 있는데 Token은 이미 만료되는 것과 같은 불일치가 발생하지 않도록 두 상태의 만료 시간과 제거 시점도 함께 설계해야 한다.
|
||||
|
||||
### 2. session affinity로 같은 인스턴스에 붙인다
|
||||
|
||||
Sticky Session을 사용하면 같은 세션의 요청을 가능한 한 동일한 애플리케이션 인스턴스로 전달할 수 있다.
|
||||
기존의 메모리 기반 세션과 Token 저장 방식을 그대로 사용할 수 있기 때문에 애플리케이션 코드의 변경이 적고 별도의 공유 저장소도 필요하지 않다.
|
||||
|
||||
하지만 해당 인스턴스가 종료되면 그 인스턴스의 메모리에 저장되어 있던 로그인 세션과 OAuth Token 정보도 함께 사라진다. 따라서 배포나 오토스케일링으로 인스턴스가 자주 교체되는 환경에서는 Sticky Session만으로 로그인 상태를 안정적으로 유지하기 어렵고, 인스턴스가 사라졌을 때 상태를 어떻게 복구할지 별도로 설계해야 한다.
|
||||
|
||||
### 3. 브라우저가 token을 들고 API를 직접 부른다
|
||||
|
||||
서버에 로그인 세션이나 OAuth Token 상태를 저장하지 않는 구조로 바꾸면, 여러 인스턴스가 공유해야 할 상태 자체가 없어지므로 별도의 공유 저장소나 Session Affinity가 필요하지 않다.
|
||||
Resource Server는 각 요청에 포함된 Access Token을 검증하여 요청을 처리한다.
|
||||
|
||||
SPA처럼 브라우저가 OAuth Token을 직접 보관하고 API 요청에 사용하는 구조가 여기에 해당한다.
|
||||
다만 브라우저에 OAuth Token을 노출하지 않아야 한다면 이 선택지는 제외한다.
|
||||
|
||||
### 4. 층이 다른 선택지 — 최소 정보만 담은 client-side cookie
|
||||
|
||||
이 방식은 기존 세션이나 Token 저장소를 다른 저장소로 교체하는 방법이 아니다.
|
||||
서버에 인증 상태를 저장하는 구조 자체를 없애고, 필요한 인증 상태를 쿠키에 담아 전달하는 방식으로 변경하는 것이다.
|
||||
따라서 공유 저장소나 Sticky Session처럼 기존 서버 상태를 어떻게 유지할지 결정하는 방법과 같이 비교하긴 어렵다.
|
||||
|
||||
Forward-Auth 구조로 전환하면 애플리케이션이 OAuth Token을 서버에 직접 저장하고 관리할 필요가 없어진다.
|
||||
대신 여러 인스턴스가 동일한 인증 쿠키를 처리할 수 있도록 Cookie Secret을 공유해야 한다.
|
||||
또한 인증 프록시가 전달하는 사용자 정보를 애플리케이션이 신뢰하게 되므로, 외부 요청이 해당 헤더를 위조할 수 없도록 네트워크 접근 경로와 전달 헤더를 함께 관리해야 한다.
|
||||
|
||||
## 다음 검증
|
||||
|
||||
인스턴스를 둘로 띄우고 순서대로 확인한다.
|
||||
|
||||
1. 한쪽에서 로그인한 뒤 다른 인스턴스로 요청을 보내 200이 유지되는지 본다.
|
||||
2. 한 인스턴스를 재시작하고 같은 session cookie로 로그인 상태가 남는지 본다.
|
||||
3. 같은 사용자로 두 브라우저에서 로그인해 authorized client 항목이 서로를 덮어쓰는지 본다.
|
||||
4. 한쪽에서 logout한 뒤 다른 쪽 요청이 어떻게 되는지 본다.
|
||||
5. session 만료를 token 만료보다 짧게, 다시 길게 두고 각 경우의 응답과 화면을 기록한다.
|
||||
|
||||
여기서 확인한 결과로 선택지를 좁힌다.
|
||||
Reference in New Issue
Block a user