- 계약 채택 — 독자 질문, 후보 29건(PROMOTE 24 · MERGE_INTO 4 · KEEP_IN_SSOT 1). 게시 중 17건은 전부 유지. 저장소 keycloak-pattern 은 패턴 넷이 브랜치로 갈라져 있어 revisions 로 tip 넷을 적었다. keycloak-session-store 는 같은 저장소 @ cdac9b8 - 게시된 기록의 redirect_uri 가 SSOT·코드와 달랐다 — OAuth2callback.html → callback.html (frontend/src/app.js 에서 확인). 계약 title 이 기록과 다른 7건도 기록 쪽으로 맞췄다 - 미작성 1건 작성 — 패턴 검증을 실제로 돌릴 때의 안전한 순서(Reference) - 리뷰 100건 반영 — 설명 뒤에 붙은 평가·차례 예고·독자 오해 가정·작성 지시를 지웠다. 삭제가 남긴 조각 4건을 고치고, 원래부터 잘려 있던 로컬 미리보기 라벨 1건도 닫았다 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
6.6 KiB
id, kind, slug, title, topic, topicName, project, status, version, questionStatus, studio, public, sourceRevision, source
| id | kind | slug | title | topic | topicName | project | status | version | questionStatus | studio | public | sourceRevision | source | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 9ae4ec71-a32e-49a7-88c2-f7368541c28d | QUESTION | refresh-rotation-replica-contention | Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가 | oauth-oidc-auth-boundary | OAuth/OIDC 인증 경계 | KeyCloak Patterns | 게시 중 | 33 | OPEN | https://hyeonworks.com/studio/documents/9ae4ec71-a32e-49a7-88c2-f7368541c28d/edit | https://hyeonworks.com/questions/refresh-rotation-replica-contention | keycloak-patterns-lab@2026-08 |
|
Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가
realm은 refresh token rotation과 재사용 허용 0회를 쓴다. 한 번 갱신하면 이전 refresh token이 바로 무효가 되기 때문에, 두 replica가 같은 refresh token으로 동시에 갱신하면 두 번째 사용이 거부될 수 있다. 실제 Keycloak 응답과 session에 미치는 영향은 아직 재현하지 않았다.
관계
- BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가 이 질문에 답하기 전에 저장소부터 정해야 한다.
- Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출 rotation과 재사용 0회를 쓰는 구성이 여기서 나왔다.
- 서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가 인스턴스를 여러 대 띄워야 이 경쟁이 생긴다.
- BFF 인증 구조 설계 기준 갱신 실패를 화면 오류로 바꾸는 규칙이 이 기준에 들어 있다.
사실
- realm에는 refresh token rotation과 재사용 허용 0회가 설정되어 있다.
- 커밋된 테스트는 새 refresh token이 발급되는지, 이전 token이 거부되는지, revocation 뒤 refresh가 실패하는지를 확인한다. 다만 이 셋은 refresh token을 직접 써서 받은 결과라, 애플리케이션이 스스로 갱신할 때도 같은 결과가 나온다고 보지 않는다.
- authorized client manager에는 refresh-token provider가 구성되어 있어, access token이 만료되면 refresh를 시도할 수 있다.
- access token이 만료될 때까지 기다려 실제로 갱신이 성공하고 새 token이 저장되는지까지는 확인하지 않았다.
- 현재 authorized client 저장소는 프로세스 안에만 있어서(process-local) replica끼리 같은 refresh token 상태를 공유하지 않는다. 그래서 이번 단일 인스턴스 검증에서는 동시 refresh 경쟁을 재현하지 않았다.
- 이미 발급된 access token은 만료 전까지 API에서 계속 통하기 때문에, 갱신이 실패해도 그동안 화면은 정상으로 보인다.
가정
- 운영에서는 replica를 둘 이상 띄우고 저장소를 공유하므로, 두 replica가 같은 authorized client 항목을 읽는다.
- 두 replica의 access token이 비슷한 시각에 만료되면 각 replica가 따로 갱신을 시도한다.
미지수
- 같은 refresh token으로 두 replica가 동시에 갱신하면 각 replica가 어떻게 동작하는가.
- 재사용 허용 0회에서 두 번째 사용이 거부되면 사용자 화면에 무엇이 보이는가. 로그인 만료로 보이는가, 일시적 오류로 보이는가.
- 저장소에서 새 token을 다시 읽어 재시도하면 성공하는가, 아니면 재인증까지 해야 하는가.
- 갱신을 한 곳에서만 할 것인가, 각자 하게 두고 실패는 재시도로 처리할 것인가.
- lock을 쓴다면 어디에 두고 얼마나 잡는가. 잡은 채로 프로세스가 내려가면 어떻게 푸는가.
- 갱신 실패를 로그인 만료와 구분해 표시할 수 있는가.
제약
- rotation과 재사용 0회는 이미 realm에 설정했다. 이 전제는 바꾸지 않고 답한다.
- 이미 발급된 access token은 만료 전까지 쓸 수 있으므로 refresh 실패가 곧바로 드러나지 않을 수 있다. 재현 테스트는 access token 만료 직후에 맞춰 실행한다.
- 이 경쟁은 저장소를 공유한 뒤에야 재현되므로 저장소를 정한 다음에 이어서 푼다.
선택지
1. 분산 lock으로 갱신을 직렬화한다
분산 lock은 여러 프로세스가 같은 자원을 동시에 건드리지 못하도록 프로세스 밖에 두는 잠금이다. 한 replica만 갱신하고 나머지는 그 갱신이 끝나기를 기다렸다가 결과를 읽는다.
대신 lock 저장소의 가용성, lock 만료, 재진입, lock을 쥔 프로세스가 내려간 상황까지 함께 처리해야 한다.
2. 각자 갱신하고 실패는 재시도로 처리한다
구현이 가장 단순하다. 지는 쪽이 거부를 받으면 저장소에서 최신 token을 다시 읽어 재시도한다는 전제인데, 이 재시도가 성립하는지부터 확인해야 한다.
같은 refresh token이 두 번 들어오는 것을 잡아내는 reuse detection 정책에 따라, 두 번째 사용이 token family 전체에 영향을 줄 수 있다. 그러면 재시도 한 번으로 끝나지 않고 재인증이 필요할 수 있다. 갱신에 성공한 replica가 새 token을 저장하기 전에 다른 replica가 저장소를 다시 읽는 순서도 따로 확인해야 한다.
3. 갱신 전용 경로를 하나 둔다
refresh를 전담하는 구성요소 하나만 refresh token을 쓰고, 다른 replica는 그 결과를 조회하게 둘 수 있다.
이 구성요소가 멈추면 access token이 만료된 뒤에 갱신할 주체가 없어진다. 그래서 이 구성요소를 어떻게 살려 두고 어떻게 복구할지를 먼저 정해야 한다.
4. 제약상 제외 — 재사용 허용을 늘린다
재사용을 짧게 허용하면 두 번째 사용이 거부되지 않고 코드도 고칠 필요가 없다. 다만 rotation과 재사용 0회는 이미 realm에 설정했고, 허용한 시간 안에는 훔친 refresh token이 들어와도 막지 못한다.
다음 검증
저장소를 공유한 뒤에 재현한다.
- replica 두 대에서 같은 사용자로 access token 만료 직후 동시에 요청을 보낸다.
- 이긴 쪽과 지는 쪽의 응답을 각각 기록한다.
- 지는 쪽이 저장된 새 token으로 재시도해 성공하는지 본다.
- 지는 쪽 사용자 화면에 무엇이 보이는지 기록한다.
- lock을 넣은 구성과 안 넣은 구성을 같은 입력으로 비교해 실패율과 지연을 잰다.
실패가 사용자에게 노출되면 lock을 고르고, 노출되지 않으면 재시도로 둔다. rotation과 재사용 0회를 바꾸는 선택지는 지금은 제외로 두고 나중에 다시 본다.