The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.9 KiB
id, kind, slug, title, topic, topicName, project, status, version, questionStatus, studio, public
| id | kind | slug | title | topic | topicName | project | status | version | questionStatus | studio | public |
|---|---|---|---|---|---|---|---|---|---|---|---|
| c72656b5-842d-45d9-b5f6-82b66b09d0b9 | QUESTION | server-session-pattern-multi-instance | 서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가 | oauth-oidc-auth-boundary | OAuth/OIDC 인증 경계 | KeyCloak Patterns | 게시 중 | 39 | OPEN | https://hyeonworks.com/studio/documents/c72656b5-842d-45d9-b5f6-82b66b09d0b9/edit | 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을 공유해야 한다. 또한 인증 프록시가 전달하는 사용자 정보를 애플리케이션이 신뢰하게 되므로, 외부 요청이 해당 헤더를 위조할 수 없도록 네트워크 접근 경로와 전달 헤더를 함께 관리해야 한다.
다음 검증
인스턴스를 둘로 띄우고 순서대로 확인한다.
- 한쪽에서 로그인한 뒤 다른 인스턴스로 요청을 보내 200이 유지되는지 본다.
- 한 인스턴스를 재시작하고 같은 session cookie로 로그인 상태가 남는지 본다.
- 같은 사용자로 두 브라우저에서 로그인해 authorized client 항목이 서로를 덮어쓰는지 본다.
- 한쪽에서 logout한 뒤 다른 쪽 요청이 어떻게 되는지 본다.
- session 만료를 token 만료보다 짧게, 다시 길게 두고 각 경우의 응답과 화면을 기록한다.
여기서 확인한 결과로 선택지를 좁힌다.