Files
keycloak-pattern/docs/experiment-c1-multi-app-sso.md
DongHyeonkaandClaude Opus 5 e856e7af4d docs: C-1 — killing the SSO session logs nobody out
One user session carries a client session per application, so visiting the second app skips the login screen. Deleting the identity provider session leaves both application sessions untouched and both apps keep serving, because the identity provider, the application session and the access token each have their own lifetime.

That inverts the B-2 finding: there the app session was cleared and the surviving SSO session let the user straight back in. Either way, clearing one side leaves the other. It also means an identity provider outage is a single point of failure for logging in, not for already-authenticated users, and the failure arrives late and all at once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:49:36 +09:00

8.9 KiB
Raw Permalink Blame History

C-1 — SSO 를 붙이면 무엇이 달라지는가

브랜치 feature/keycloak-c1-multi-app-sso · 증거 docs/evidence/c1-multi-app-sso/ · 2026-09-04 16:1516:30 KST

선행: B-2 (app1 = BFF) · B-7 (app2 = oauth2-proxy)

원래 질문"SSO 를 추가하게 되면 어떻게 달라지는지"


0. 결론부터

물음
두 번째 앱에서 로그인 화면이 뜨는가 안 뜬다. SSO 가 동작한다
Keycloak 안의 구조는 user session 1개 · client session 2개
IdP 세션을 죽이면 두 앱이 끊기는가 ★ 안 끊긴다. 두 앱 모두 그대로 동작한다

"SSO 를 붙이면 IdP 가 단일 장애점이 된다" 는 절반만 맞다. 로그인할 때는 그렇고, 이미 로그인한 뒤에는 아니다.


1. 구성 — 서로 다른 구조의 두 앱

   app1.hyeonworks.com  →  BFF          서버 세션 (Redis)  +  토큰 (PostgreSQL)
   app2.hyeonworks.com  →  oauth2-proxy 쿠키 티켓         +  세션 (Redis)

                    둘 다 realm keycloak-patterns

우연히 좋은 실험대가 됐다. B-2 와 B-7 에서 만든 두 앱이 같은 IdP 를 쓰지만 세션을 완전히 다르게 다룬다.

깨끗한 상태에서 시작한다

kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
  -c "delete from offline_client_session" -c "delete from offline_user_session"
kubectl -n keycloak-lab exec deploy/redis -- redis-cli flushall
kubectl -n keycloak-lab rollout restart statefulset/keycloak

kcadm create realms/.../logout-all 은 듣지 않았다. 세션이 그대로 남아 DB 를 직접 지우고 Keycloak 을 재시작해야 했다 — 캐시 때문이다 (A-1 에서 확인한 대로, DB 를 직접 지워도 캐시는 남는다).

  Keycloak 온라인 세션: 0
  app1 HTTP 200 / app2 HTTP 200

2. SSO 가 동작한다

app1 로그인 — 로그인 화면이 나온다

https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/auth
  ?client_id=bff-confidential&...
→ Sign in to keycloak-patterns

로그인 후

     user_session_id      | client_sessions
--------------------------+-----------------
 oqOjHekin4JU-BZjgQLjUByW |               1

  Redis: bff:session:sessions:6e0d9af4-...
  PostgreSQL authorized client: 1 행

app2 방문 — 로그인 화면 없이 통과한다

app2 가 로그인 없이 열린다

     user_session_id      | client_sessions
--------------------------+-----------------
 oqOjHekin4JU-BZjgQLjUByW |               2      ← 1 → 2

              client_id               |       name
--------------------------------------+------------------
 9055fa46-6abb-4d6d-a339-8a9183bbf26d | bff-confidential
 80431dbc-af81-4673-9790-ad06d1570b2e | oauth2-proxy

  Redis:
    bff:session:sessions:6e0d9af4-...      ← app1 의 세션
    _oauth2_proxy-6b028a70f...             ← app2 의 세션

개념 — SSO 의 데이터 구조

   user session  (사용자 · 브라우저 하나당 하나)
     ├─ client session : bff-confidential
     └─ client session : oauth2-proxy

A-3 과 B-3 에서 봤던 그 구조가 여기서 의미를 갖는다.

  • A-3 — 크래시로 user_session 행이 통째로 사라지면 모든 앱이 끊긴다
  • B-3 — 재사용 탐지가 client_session 제거하면 그 앱만 끊긴다

두 층이 나뉘어 있는 이유가 SSO 다. 앱 하나의 문제가 다른 앱에 번지지 않도록 하려면 client session 이 따로 있어야 한다.


3. ★ IdP 세션을 죽여도 두 앱은 살아 있다

kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
  create users/<user-id>/logout -r keycloak-patterns
  남은 keycloak-patterns 세션: 0        (master realm 의 admin 세션만 남음)

  Redis:
    _oauth2_proxy-6b028a70f...           ← 남아 있다
    bff:session:sessions:6e0d9af4-...    ← 남아 있다
  PostgreSQL authorized client: 1 행     ← 남아 있다

브라우저로 두 앱을 다시 열었다.

IdP 세션이 없어도 앱은 동작한다

둘 다 로그인 화면 없이 그대로 열렸다.

왜 그런가 — 세 개의 독립된 수명

   ① IdP 세션 (Keycloak)          ssoSessionIdleTimeout 1800초
   ② 앱 세션  (BFF / oauth2-proxy) 각자 30분 / 1시간
   ③ access token                 60초

   ①을 지워도 ②는 자기 수명을 산다

로그아웃은 ①만 지운 것이고, ②는 아무도 안 건드렸다.

언제 끊기는가
BFF access token 이 만료되어 refresh 를 시도할 때Session not active
oauth2-proxy 쿠키 만료(1시간) 또는 토큰 갱신 시도할 때

즉시가 아니라 지연되어 끊긴다. 최대 지연은 access token 수명(60초)이 아니라 앱이 다음에 IdP 를 부를 때까지다.

이것이 B-2 에서 "로그아웃했는데 다시 들어가진다" 를 겪은 것의 반대편이다. 거기서는 앱 세션을 지웠는데 IdP 세션이 남아 재로그인이 됐고, 여기서는 IdP 세션을 지웠는데 앱 세션이 남아 계속 들어가진다.

두 방향 모두 "한쪽만 지우면 다른 쪽이 남는다" 이다.


4. 그래서 SSO 의 대가는 무엇인가

원래 질문 "SSO 를 추가하면 어떻게 달라지는가" 에 대한 답이다.

앱이 하나일 때 SSO 일 때
로그인 앱마다 한 번
IdP 가 죽으면 그 앱만 로그인 불가 모든 앱이 로그인 불가
이미 로그인한 사용자 ★ 영향 없다 (앱 세션이 살아 있으므로)
로그아웃 그 앱만 전 앱을 끊으려면 백채널 로그아웃이 필요
세션 수명 하나 세 층이 각자 — 어긋나면 예측이 어렵다

IdP 는 "로그인 경로"의 단일 장애점이지 "이미 로그인한 사용자"의 단일 장애점이 아니다. A-2(DB 상실)에서 본 것과 합치면

   Keycloak DB 죽음  →  새 로그인 불가 (전 앱)
                     →  이미 로그인한 사용자는 앱 세션 수명 동안 계속 쓴다
                     →  그 뒤 갱신 시점에 한꺼번에 끊긴다

장애가 즉시 전면화되지 않고 "앱 세션 수명만큼 지연되어 몰려온다." 이것이 SSO 구조의 장애 모양이며, 모니터링이 어려운 이유다.


5. 겪은 문제

문제
kcadm create realms/<r>/logout-all 이 안 먹었다 세션 수가 그대로였다. DB 를 직접 지우고 Keycloak 재시작해야 했다 (A-1 의 캐시 문제)
kcadm delete sessions/<id> 도 안 먹었다 오류 없이 아무 일도 안 일어났다. users/<id>/logout 은 동작했다
세션 수를 셀 때 realm 을 안 봤다 master realm 의 admin 세션이 섞여 "안 지워졌다" 로 오독할 뻔했다

세 번째가 특히 위험했다 — realm 을 join 해서 보고 나서야 알았다.

select us.user_session_id, r.name as realm, ...
  from offline_user_session us join realm r on r.id = us.realm_id

6. 재현 절차 (명령어)

# 1. 깨끗한 상태 — logout-all 은 안 먹으므로 DB + 재시작
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
  -c "delete from offline_client_session" -c "delete from offline_user_session"
kubectl -n keycloak-lab exec deploy/redis -- redis-cli flushall
kubectl -n keycloak-lab rollout restart statefulset/keycloak

# 2. app1 로그인 → app2 방문 (로그인 화면이 안 떠야 SSO)

# 3. 구조 확인 — user session 1 에 client session 2
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c \
  "select us.user_session_id, r.name as realm,
          (select count(*) from offline_client_session cs
            where cs.user_session_id=us.user_session_id) as clients
     from offline_user_session us join realm r on r.id=us.realm_id
    where us.offline_flag='0'"

# 4. IdP 세션만 죽인다
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
  create users/<user-id>/logout -r keycloak-patterns

# 5. 두 앱을 다시 연다 — 그대로 열리면 앱 세션이 독립적이라는 뜻

7. 다음 실험에 남기는 것

실험 이 실험이 준 것
C-2 백채널 로그아웃 이 실험이 C-2 가 왜 필요한지 보여준다 — IdP 로그아웃이 앱에 전파되지 않는다
D-1 백업·복구 user session 을 잃으면 전 앱이 끊긴다 — 백업 범위에 들어간다
운영 세 층의 수명을 맞추거나, 어긋날 때의 동작을 정의해야 한다