Files
document-haness/.playwright-mcp/page-2026-08-26T11-46-10-109Z.yml
T

515 lines
40 KiB
YAML

- generic [ref=f16e3]:
- link "본문으로 건너뛰기" [ref=f16e4] [cursor=pointer]:
- /url: "#main-content"
- banner [ref=f16e5]:
- generic [ref=f16e6]:
- link "TechLog Studio" [ref=f16e7] [cursor=pointer]:
- /url: /studio
- text: TechLog
- generic [ref=f16e8]: Studio
- navigation "Studio 주 탐색" [ref=f16e10]:
- link "작업본" [ref=f16e11] [cursor=pointer]:
- /url: /studio/documents
- link "게시 기록" [ref=f16e12] [cursor=pointer]:
- /url: /studio/publications
- link "새 문서" [ref=f16e13] [cursor=pointer]:
- /url: /studio/documents/new
- link "주제·프로젝트" [ref=f16e14] [cursor=pointer]:
- /url: /studio/taxonomy
- link "릴리즈" [ref=f16e15] [cursor=pointer]:
- /url: /studio/releases
- link "공개 사이트 보기" [ref=f16e16] [cursor=pointer]:
- /url: /
- button "로그아웃" [ref=f16e17]
- main [ref=f16e18]:
- generic [ref=f16e19]:
- generic [ref=f16e20]:
- region [ref=f16e21]:
- generic [ref=f16e22]:
- paragraph [ref=f16e23]: REFERENCE · VERSION 12
- heading "문서 편집" [level=1] [ref=f16e24]
- paragraph [ref=f16e25]: OAuth Token과 Application Session을 구분하는 기준
- region [ref=f16e26]:
- generic [ref=f16e27]:
- paragraph [ref=f16e28]: DOCUMENT
- heading "기본 정보" [level=2] [ref=f16e29]
- generic [ref=f16e30]:
- generic [ref=f16e31]:
- generic [ref=f16e32]: 제목
- textbox "제목" [ref=f16e33]: OAuth Token과 Application Session을 구분하는 기준
- generic [ref=f16e34]:
- generic [ref=f16e35]: slug
- textbox "slug" [ref=f16e36]:
- /placeholder: 비우면 제목에서 만듭니다 (영문 소문자·숫자·하이픈)
- text: oauth-token-application-session-boundary
- generic [ref=f16e37]:
- generic [ref=f16e38]: 요약
- textbox "요약" [ref=f16e39]: IdP의 SSO session, access token, refresh token, 애플리케이션 session cookie, proxy session cookie는 만든 주체도 소비자도 수명도 다르다. 다섯을 로그인 상태 하나로 부르면 무엇이 만료됐고 무엇을 지워야 하는지 말할 수 없게 된다.
- generic [ref=f16e40]:
- generic [ref=f16e41]: Topic
- combobox "Topic" [ref=f16e42]:
- option "선택하지 않음"
- option "OAuth/OIDC 인증 경계" [selected]
- generic [ref=f16e43]:
- generic [ref=f16e44]: Project
- combobox "Project" [ref=f16e45]:
- option "미지정"
- option "Backend Clean Architecture"
- option "KeyCloak Patterns" [selected]
- option "Liner N + 1문제"
- group "관계" [ref=f16e46]:
- generic [ref=f16e48]:
- generic [ref=f16e49]:
- generic [ref=f16e50]: 관계 1 대상
- combobox "관계 1 대상" [ref=f16e51]:
- option "대상 선택"
- option "인증 구조를 보안 성숙도 단계로 취급하지 않는다"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정" [disabled]
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" [disabled]
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP Federation을 별도의 인증 구조로 세지 않는다"
- option "외부 IdP Federation과 Application 인증 경계"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준"
- option "Public Client와 Confidential Client 구분 기준"
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출" [disabled]
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" [selected]
- generic [ref=f16e52]:
- generic [ref=f16e53]: 관계 1 이유
- textbox "관계 1 이유" [ref=f16e54]: JavaScript memory의 OAuth token과 Keycloak SSO session을 구분한 Case다.
- generic [ref=f16e55]:
- button "위로" [disabled] [ref=f16e56]
- button "아래로" [ref=f16e57]
- button "삭제" [ref=f16e58]
- generic [ref=f16e59]:
- generic [ref=f16e60]:
- generic [ref=f16e61]: 관계 2 대상
- combobox "관계 2 대상" [ref=f16e62]:
- option "대상 선택"
- option "인증 구조를 보안 성숙도 단계로 취급하지 않는다"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정" [disabled]
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" [disabled]
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP Federation을 별도의 인증 구조로 세지 않는다"
- option "외부 IdP Federation과 Application 인증 경계"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준"
- option "Public Client와 Confidential Client 구분 기준"
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출" [selected]
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" [disabled]
- generic [ref=f16e63]:
- generic [ref=f16e64]: 관계 2 이유
- textbox "관계 2 이유" [ref=f16e65]: 같은 요청 안에서 session cookie와 access token이 함께 움직인다.
- generic [ref=f16e66]:
- button "위로" [ref=f16e67]
- button "아래로" [ref=f16e68]
- button "삭제" [ref=f16e69]
- generic [ref=f16e70]:
- generic [ref=f16e71]:
- generic [ref=f16e72]: 관계 3 대상
- combobox "관계 3 대상" [ref=f16e73]:
- option "대상 선택"
- option "인증 구조를 보안 성숙도 단계로 취급하지 않는다"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정" [selected]
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" [disabled]
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP Federation을 별도의 인증 구조로 세지 않는다"
- option "외부 IdP Federation과 Application 인증 경계"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준"
- option "Public Client와 Confidential Client 구분 기준"
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출" [disabled]
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" [disabled]
- generic [ref=f16e74]:
- generic [ref=f16e75]: 관계 3 이유
- textbox "관계 3 이유" [ref=f16e76]: BFF에서는 session cookie, JavaScript가 읽는 CSRF token, server-side OAuth token을 각각 다른 용도로 사용한다.
- generic [ref=f16e77]:
- button "위로" [ref=f16e78]
- button "아래로" [ref=f16e79]
- button "삭제" [ref=f16e80]
- generic [ref=f16e81]:
- generic [ref=f16e82]:
- generic [ref=f16e83]: 관계 4 대상
- combobox "관계 4 대상" [ref=f16e84]:
- option "대상 선택"
- option "인증 구조를 보안 성숙도 단계로 취급하지 않는다"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정" [disabled]
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" [selected]
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP Federation을 별도의 인증 구조로 세지 않는다"
- option "외부 IdP Federation과 Application 인증 경계"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준"
- option "Public Client와 Confidential Client 구분 기준"
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출" [disabled]
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" [disabled]
- generic [ref=f16e85]:
- generic [ref=f16e86]: 관계 4 이유
- textbox "관계 4 이유" [ref=f16e87]: Forward-Auth에서는 upstream이 JWT를 직접 검증하지 않고 proxy session을 기반으로 edge가 만든 identity header를 사용한다.
- generic [ref=f16e88]:
- button "위로" [ref=f16e89]
- button "아래로" [disabled] [ref=f16e90]
- button "삭제" [ref=f16e91]
- button "관계 추가" [ref=f16e92]
- region [ref=f16e93]:
- generic [ref=f16e94]:
- paragraph [ref=f16e95]: REFERENCE
- heading "재사용할 기준" [level=2] [ref=f16e96]
- generic [ref=f16e97]:
- generic [ref=f16e98]: 목적
- textbox "목적" [ref=f16e99]: 네 구조를 다 실행해 보면 응답에는 모두 같은 사용자 이름이 나오게 되어서 같은 인증 정보라고 묶기 쉽다. 그런데 값이 들어온 곳을 따라가 보면 어떤 때는 JWT 안의 claim이고 어떤 때는 proxy가 만든 헤더다. 둘을 다 로그인 상태라고 부르게 되면 서명을 검증한 것인지 헤더를 확인한 것인지 문장만 봐서는 구분할 수 없게 된다. 로그아웃과 만료 처리는 credential마다 다르다. 어떤 상태를 삭제하거나 만료시킬지 정하려면 IdP SSO session, OAuth token, application session을 구분해서 다뤄야 한다.
- group "규칙" [ref=f16e100]:
- generic [ref=f16e102]:
- generic [ref=f16e103]:
- generic [ref=f16e104]: 규칙 1 제목
- textbox "규칙 1 제목" [ref=f16e105]: 다섯 상태에 각각 다른 이름을 쓴다
- generic [ref=f16e106]:
- generic [ref=f16e107]: 규칙 1 본문
- textbox "규칙 1 본문" [ref=f16e108]: "IdP SSO session, OAuth access token, OAuth refresh token, 애플리케이션 session cookie, proxy session cookie는 서로 다른 것이라서 문서와 코드, 로그에서 같은 이름을 돌려 쓰지 않는다. 로그와 진단 정보에서도 `로그인 상태`라는 표현만 쓰지 않고 실제 session 또는 token 종류를 기록한다."
- generic [ref=f16e109]:
- button "위로" [disabled] [ref=f16e110]
- button "아래로" [ref=f16e111]
- button "삭제" [ref=f16e112]
- generic [ref=f16e113]:
- generic [ref=f16e114]:
- generic [ref=f16e115]: 규칙 2 제목
- textbox "규칙 2 제목" [ref=f16e116]: 만든 주체와 주된 소비자로 구분한다
- generic [ref=f16e117]:
- generic [ref=f16e118]: 규칙 2 본문
- textbox "규칙 2 본문" [ref=f16e119]: access token은 IdP가 만들고 Resource Server가 소비하게 되고, 애플리케이션 session cookie는 애플리케이션이 만들어 자기 로그인 상태를 찾는 데 쓰게 되며, proxy session cookie는 proxy의 auth endpoint에만 제시된다. 화면에 같은 사용자 이름이 보이더라도 credential을 발급한 주체와 검증하는 주체가 다르면 별도의 상태로 다룬다.
- generic [ref=f16e120]:
- button "위로" [ref=f16e121]
- button "아래로" [ref=f16e122]
- button "삭제" [ref=f16e123]
- generic [ref=f16e124]:
- generic [ref=f16e125]:
- generic [ref=f16e126]: 규칙 3 제목
- textbox "규칙 3 제목" [ref=f16e127]: cookie가 token을 담고 있다고 쓰지 않는다
- generic [ref=f16e128]:
- generic [ref=f16e129]: 규칙 3 본문
- textbox "규칙 3 본문" [ref=f16e130]: 애플리케이션 session cookie는 server-side 상태를 찾는 열쇠다. 실제 access token과 refresh token은 별도 store에 있어서 cookie 안에는 없다. proxy session cookie는 같은 모델이 아니다. 서버에 상태를 두지 않고 최소 정보를 cookie 자체에 담아 proxy가 검증하는 구성일 수 있다. 두 cookie를 같은 문장으로 설명하지 않는다. cookie를 token map의 직렬화라고 설명하게 되면 구현 설명이 틀리게 되고, 그 store를 어디에 둘지가 별도 문제라는 것도 함께 가려지게 된다.
- generic [ref=f16e131]:
- button "위로" [ref=f16e132]
- button "아래로" [ref=f16e133]
- button "삭제" [ref=f16e134]
- generic [ref=f16e135]:
- generic [ref=f16e136]:
- generic [ref=f16e137]: 규칙 4 제목
- textbox "규칙 4 제목" [ref=f16e138]: 브라우저에 없다는 말의 대상을 밝힌다
- generic [ref=f16e139]:
- generic [ref=f16e140]: 규칙 4 본문
- textbox "규칙 4 본문" [ref=f16e141]: 브라우저 JavaScript에 OAuth token을 전달하지 않는 구조에서도 인증 상태는 존재한다. BFF의 HttpOnly session cookie나 IdP 도메인의 SSO cookie는 각각 별도로 유지될 수 있다. 무엇이 없는지를 적지 않으면 브라우저에 인증 상태가 아예 없다는 뜻으로 읽힌다.
- generic [ref=f16e142]:
- button "위로" [ref=f16e143]
- button "아래로" [ref=f16e144]
- button "삭제" [ref=f16e145]
- generic [ref=f16e146]:
- generic [ref=f16e147]:
- generic [ref=f16e148]: 규칙 5 제목
- textbox "규칙 5 제목" [ref=f16e149]: 영구 저장소에 없는 것과 실행 중에 없는 것을 나눈다
- generic [ref=f16e150]:
- generic [ref=f16e151]: 규칙 5 본문
- textbox "규칙 5 본문" [ref=f16e152]: OAuth token을 JavaScript memory에만 보관하면 Web Storage에 지속적으로 저장하지는 않는다. 실행 중 같은 origin의 script가 응답이나 지역 변수에 접근하는 문제는 별도다. 두 문장을 같은 증거로 쓰게 되면 XSS 위험이 줄었다는 잘못된 결론이 나오게 된다.
- generic [ref=f16e153]:
- button "위로" [ref=f16e154]
- button "아래로" [ref=f16e155]
- button "삭제" [ref=f16e156]
- generic [ref=f16e157]:
- generic [ref=f16e158]:
- generic [ref=f16e159]: 규칙 6 제목
- textbox "규칙 6 제목" [ref=f16e160]: 로그아웃 범위를 상태별로 적는다
- generic [ref=f16e161]:
- generic [ref=f16e162]: 규칙 6 본문
- textbox "규칙 6 본문" [ref=f16e163]: 애플리케이션 상태를 지우는 것과 IdP session을 끝내는 것은 다르고, 이미 발급된 self-contained JWT는 만료 전까지 API에서 계속 통하게 된다. self-contained JWT를 stateless하게 검증하면서 denylist나 introspection을 사용하지 않는 구성에서는 애플리케이션 logout만으로 이미 발급된 access token을 즉시 무효화할 수 없다. 이 경우 짧은 access token TTL을 사용해 유효 시간을 제한한다.
- generic [ref=f16e164]:
- button "위로" [ref=f16e165]
- button "아래로" [ref=f16e166]
- button "삭제" [ref=f16e167]
- generic [ref=f16e168]:
- generic [ref=f16e169]:
- generic [ref=f16e170]: 규칙 7 제목
- textbox "규칙 7 제목" [ref=f16e171]: Logout 대상 credential을 구체적으로 적는다
- generic [ref=f16e172]:
- generic [ref=f16e173]: 규칙 7 본문
- textbox "규칙 7 본문" [ref=f16e174]: SPA의 JavaScript memory를 초기화해도 Keycloak SSO session이 유효하면 다음 authorization request에서 다시 인증 화면을 생략할 수 있다. logout에서는 application session과 authorized client를 각각 어떻게 정리할지 명시한다.
- generic [ref=f16e175]:
- button "위로" [ref=f16e176]
- button "아래로" [disabled] [ref=f16e177]
- button "삭제" [ref=f16e178]
- button "규칙 추가" [ref=f16e179]
- group "적용 조건" [ref=f16e180]:
- generic [ref=f16e182]:
- generic [ref=f16e183]:
- generic [ref=f16e184]: 적용 조건 1
- textbox "적용 조건 1" [ref=f16e185]: 인증 상태를 표나 문서로 정리할 때
- generic [ref=f16e186]:
- button "위로" [disabled] [ref=f16e187]
- button "아래로" [ref=f16e188]
- button "삭제" [ref=f16e189]
- generic [ref=f16e190]:
- generic [ref=f16e191]:
- generic [ref=f16e192]: 적용 조건 2
- textbox "적용 조건 2" [ref=f16e193]: 로그아웃과 만료 동작을 설계할 때
- generic [ref=f16e194]:
- button "위로" [ref=f16e195]
- button "아래로" [ref=f16e196]
- button "삭제" [ref=f16e197]
- generic [ref=f16e198]:
- generic [ref=f16e199]:
- generic [ref=f16e200]: 적용 조건 3
- textbox "적용 조건 3" [ref=f16e201]: 브라우저가 어떤 credential을 저장하거나 전송하는지 설명할 때
- generic [ref=f16e202]:
- button "위로" [ref=f16e203]
- button "아래로" [ref=f16e204]
- button "삭제" [ref=f16e205]
- generic [ref=f16e206]:
- generic [ref=f16e207]:
- generic [ref=f16e208]: 적용 조건 4
- textbox "적용 조건 4" [ref=f16e209]: 여러 구조를 같은 항목으로 비교할 때
- generic [ref=f16e210]:
- button "위로" [ref=f16e211]
- button "아래로" [disabled] [ref=f16e212]
- button "삭제" [ref=f16e213]
- button "적용 조건 추가" [ref=f16e214]
- group "예외" [ref=f16e215]:
- generic [ref=f16e217]:
- generic [ref=f16e218]:
- generic [ref=f16e219]: 예외 1
- textbox "예외 1" [ref=f16e220]: 한 요청 안에서 어느 상태를 말하는지 문맥으로 이미 분명하면 짧은 이름을 쓸 수 있다. 그때도 문서에서 처음 나올 때는 전체 이름을 적어 둔다.
- generic [ref=f16e221]:
- button "위로" [disabled] [ref=f16e222]
- button "아래로" [ref=f16e223]
- button "삭제" [ref=f16e224]
- generic [ref=f16e225]:
- generic [ref=f16e226]:
- generic [ref=f16e227]: 예외 2
- textbox "예외 2" [ref=f16e228]: IdP를 쓰지 않고 애플리케이션이 자체 로그인만 하는 구조에는 SSO session과 access token, refresh token이 없다.
- generic [ref=f16e229]:
- button "위로" [ref=f16e230]
- button "아래로" [disabled] [ref=f16e231]
- button "삭제" [ref=f16e232]
- button "예외 추가" [ref=f16e233]
- group "예시" [ref=f16e234]:
- generic [ref=f16e236]:
- generic [ref=f16e237]:
- generic [ref=f16e238]: 예시 1
- textbox "예시 1" [ref=f16e239]: "IdP SSO session : IdP 도메인의 cookie이고 애플리케이션 memory와 별개다"
- generic [ref=f16e240]:
- button "위로" [disabled] [ref=f16e241]
- button "아래로" [ref=f16e242]
- button "삭제" [ref=f16e243]
- generic [ref=f16e244]:
- generic [ref=f16e245]:
- generic [ref=f16e246]: 예시 2
- textbox "예시 2" [ref=f16e247]: "access token : IdP가 만들고 Resource Server가 서명과 issuer, audience를 검증한다"
- generic [ref=f16e248]:
- button "위로" [ref=f16e249]
- button "아래로" [ref=f16e250]
- button "삭제" [ref=f16e251]
- generic [ref=f16e252]:
- generic [ref=f16e253]:
- generic [ref=f16e254]: 예시 3
- textbox "예시 3" [ref=f16e255]: "refresh token : 새 access token을 받는 장기 credential이다"
- generic [ref=f16e256]:
- button "위로" [ref=f16e257]
- button "아래로" [ref=f16e258]
- button "삭제" [ref=f16e259]
- generic [ref=f16e260]:
- generic [ref=f16e261]:
- generic [ref=f16e262]: 예시 4
- textbox "예시 4" [ref=f16e263]: "애플리케이션 session cookie : server-side 로그인 상태를 찾는 열쇠다"
- generic [ref=f16e264]:
- button "위로" [ref=f16e265]
- button "아래로" [ref=f16e266]
- button "삭제" [ref=f16e267]
- generic [ref=f16e268]:
- generic [ref=f16e269]:
- generic [ref=f16e270]: 예시 5
- textbox "예시 5" [ref=f16e271]: "proxy session cookie : proxy의 auth endpoint에 제시하는 최소 상태다"
- generic [ref=f16e272]:
- button "위로" [ref=f16e273]
- button "아래로" [ref=f16e274]
- button "삭제" [ref=f16e275]
- generic [ref=f16e276]:
- generic [ref=f16e277]:
- generic [ref=f16e278]: 예시 6
- textbox "예시 6" [ref=f16e279]: "CSRF token : cookie가 자동으로 붙는 상태 변경 요청의 의도를 확인한다"
- generic [ref=f16e280]:
- button "위로" [ref=f16e281]
- button "아래로" [ref=f16e282]
- button "삭제" [ref=f16e283]
- generic [ref=f16e284]:
- generic [ref=f16e285]:
- generic [ref=f16e286]: 예시 7
- textbox "예시 7" [ref=f16e287]: "identity header : edge가 확인한 사용자 정보의 투영이고 JWT가 아니다"
- generic [ref=f16e288]:
- button "위로" [ref=f16e289]
- button "아래로" [disabled] [ref=f16e290]
- button "삭제" [ref=f16e291]
- button "예시 추가" [ref=f16e292]
- generic [ref=f16e293]:
- generic [ref=f16e294]: 마지막 검증일
- textbox "마지막 검증일" [ref=f16e295]
- region [ref=f16e296]:
- generic [ref=f16e297]:
- paragraph [ref=f16e298]: LIVE
- heading "즉시 미리보기" [level=2] [ref=f16e299]
- generic [ref=f16e302]:
- generic [ref=f16e303]:
- navigation "문서 경로" [ref=f16e304]:
- link "Reference" [ref=f16e305] [cursor=pointer]:
- /url: /explore/references
- generic [ref=f16e306]: /
- generic [ref=f16e307]: OAuth/OIDC 인증 경계
- generic [ref=f16e308]: /
- link "KeyCloak Patterns" [ref=f16e309] [cursor=pointer]:
- /url: /projects/keycloak-patterns
- heading "OAuth Token과 Application Session을 구분하는 기준" [level=1] [ref=f16e310]
- paragraph [ref=f16e311]: IdP의 SSO session, access token, refresh token, 애플리케이션 session cookie, proxy session cookie는 만든 주체도 소비자도 수명도 다르다. 다섯을 로그인 상태 하나로 부르면 무엇이 만료됐고 무엇을 지워야 하는지 말할 수 없게 된다.
- generic [ref=f16e312]:
- generic [ref=f16e313]:
- term [ref=f16e314]: 유형
- definition [ref=f16e315]: Reference
- generic [ref=f16e316]:
- term [ref=f16e317]: 프로젝트
- definition [ref=f16e318]: KeyCloak Patterns
- generic [ref=f16e319]:
- term [ref=f16e320]: 게시
- definition [ref=f16e321]: 게시 전
- region [ref=f16e322]:
- paragraph [ref=f16e323]: Purpose
- heading "이 기준을 쓰는 이유" [level=2] [ref=f16e324]
- paragraph [ref=f16e325]: 네 구조를 다 실행해 보면 응답에는 모두 같은 사용자 이름이 나오게 되어서 같은 인증 정보라고 묶기 쉽다.그런데 값이 들어온 곳을 따라가 보면 어떤 때는 JWT 안의 claim이고 어떤 때는 proxy가 만든 헤더다. 둘을 다 로그인 상태라고 부르게 되면 서명을 검증한 것인지 헤더를 확인한 것인지 문장만 봐서는 구분할 수 없게 된다.로그아웃과 만료 처리는 credential마다 다르다. 어떤 상태를 삭제하거나 만료시킬지 정하려면 IdP SSO session, OAuth token, application session을 구분해서 다뤄야 한다.
- article [ref=f16e326]:
- region [ref=f16e327]:
- heading "판단 기준" [level=2] [ref=f16e328]
- list [ref=f16e329]:
- listitem [ref=f16e330]:
- generic [ref=f16e331]: "01"
- generic [ref=f16e332]:
- heading "다섯 상태에 각각 다른 이름을 쓴다" [level=3] [ref=f16e333]
- paragraph [ref=f16e334]: "IdP SSO session, OAuth access token, OAuth refresh token, 애플리케이션 session cookie, proxy session cookie는 서로 다른 것이라서 문서와 코드, 로그에서 같은 이름을 돌려 쓰지 않는다. 로그와 진단 정보에서도 `로그인 상태`라는 표현만 쓰지 않고 실제 session 또는 token 종류를 기록한다."
- listitem [ref=f16e335]:
- generic [ref=f16e336]: "02"
- generic [ref=f16e337]:
- heading "만든 주체와 주된 소비자로 구분한다" [level=3] [ref=f16e338]
- paragraph [ref=f16e339]: access token은 IdP가 만들고 Resource Server가 소비하게 되고, 애플리케이션 session cookie는 애플리케이션이 만들어 자기 로그인 상태를 찾는 데 쓰게 되며, proxy session cookie는 proxy의 auth endpoint에만 제시된다. 화면에 같은 사용자 이름이 보이더라도 credential을 발급한 주체와 검증하는 주체가 다르면 별도의 상태로 다룬다.
- listitem [ref=f16e340]:
- generic [ref=f16e341]: "03"
- generic [ref=f16e342]:
- heading "cookie가 token을 담고 있다고 쓰지 않는다" [level=3] [ref=f16e343]
- paragraph [ref=f16e344]: 애플리케이션 session cookie는 server-side 상태를 찾는 열쇠다. 실제 access token과 refresh token은 별도 store에 있어서 cookie 안에는 없다. proxy session cookie는 같은 모델이 아니다. 서버에 상태를 두지 않고 최소 정보를 cookie 자체에 담아 proxy가 검증하는 구성일 수 있다. 두 cookie를 같은 문장으로 설명하지 않는다. cookie를 token map의 직렬화라고 설명하게 되면 구현 설명이 틀리게 되고, 그 store를 어디에 둘지가 별도 문제라는 것도 함께 가려지게 된다.
- listitem [ref=f16e345]:
- generic [ref=f16e346]: "04"
- generic [ref=f16e347]:
- heading "브라우저에 없다는 말의 대상을 밝힌다" [level=3] [ref=f16e348]
- paragraph [ref=f16e349]: 브라우저 JavaScript에 OAuth token을 전달하지 않는 구조에서도 인증 상태는 존재한다. BFF의 HttpOnly session cookie나 IdP 도메인의 SSO cookie는 각각 별도로 유지될 수 있다. 무엇이 없는지를 적지 않으면 브라우저에 인증 상태가 아예 없다는 뜻으로 읽힌다.
- listitem [ref=f16e350]:
- generic [ref=f16e351]: "05"
- generic [ref=f16e352]:
- heading "영구 저장소에 없는 것과 실행 중에 없는 것을 나눈다" [level=3] [ref=f16e353]
- paragraph [ref=f16e354]: OAuth token을 JavaScript memory에만 보관하면 Web Storage에 지속적으로 저장하지는 않는다. 실행 중 같은 origin의 script가 응답이나 지역 변수에 접근하는 문제는 별도다. 두 문장을 같은 증거로 쓰게 되면 XSS 위험이 줄었다는 잘못된 결론이 나오게 된다.
- listitem [ref=f16e355]:
- generic [ref=f16e356]: "06"
- generic [ref=f16e357]:
- heading "로그아웃 범위를 상태별로 적는다" [level=3] [ref=f16e358]
- paragraph [ref=f16e359]: 애플리케이션 상태를 지우는 것과 IdP session을 끝내는 것은 다르고, 이미 발급된 self-contained JWT는 만료 전까지 API에서 계속 통하게 된다. self-contained JWT를 stateless하게 검증하면서 denylist나 introspection을 사용하지 않는 구성에서는 애플리케이션 logout만으로 이미 발급된 access token을 즉시 무효화할 수 없다. 이 경우 짧은 access token TTL을 사용해 유효 시간을 제한한다.
- listitem [ref=f16e360]:
- generic [ref=f16e361]: "07"
- generic [ref=f16e362]:
- heading "Logout 대상 credential을 구체적으로 적는다" [level=3] [ref=f16e363]
- paragraph [ref=f16e364]: SPA의 JavaScript memory를 초기화해도 Keycloak SSO session이 유효하면 다음 authorization request에서 다시 인증 화면을 생략할 수 있다. logout에서는 application session과 authorized client를 각각 어떻게 정리할지 명시한다.
- region [ref=f16e365]:
- heading "적용할 때" [level=2] [ref=f16e366]
- list [ref=f16e367]:
- listitem [ref=f16e368]: 인증 상태를 표나 문서로 정리할 때
- listitem [ref=f16e369]: 로그아웃과 만료 동작을 설계할 때
- listitem [ref=f16e370]: 브라우저가 어떤 credential을 저장하거나 전송하는지 설명할 때
- listitem [ref=f16e371]: 여러 구조를 같은 항목으로 비교할 때
- region [ref=f16e372]:
- heading "예외와 주의" [level=2] [ref=f16e373]
- list [ref=f16e374]:
- listitem [ref=f16e375]: 한 요청 안에서 어느 상태를 말하는지 문맥으로 이미 분명하면 짧은 이름을 쓸 수 있다. 그때도 문서에서 처음 나올 때는 전체 이름을 적어 둔다.
- listitem [ref=f16e376]: IdP를 쓰지 않고 애플리케이션이 자체 로그인만 하는 구조에는 SSO session과 access token, refresh token이 없다.
- region [ref=f16e377]:
- heading "예시" [level=2] [ref=f16e378]
- list [ref=f16e379]:
- listitem [ref=f16e380]: "IdP SSO session : IdP 도메인의 cookie이고 애플리케이션 memory와 별개다"
- listitem [ref=f16e381]: "access token : IdP가 만들고 Resource Server가 서명과 issuer, audience를 검증한다"
- listitem [ref=f16e382]: "refresh token : 새 access token을 받는 장기 credential이다"
- listitem [ref=f16e383]: "애플리케이션 session cookie : server-side 로그인 상태를 찾는 열쇠다"
- listitem [ref=f16e384]: "proxy session cookie : proxy의 auth endpoint에 제시하는 최소 상태다"
- listitem [ref=f16e385]: "CSRF token : cookie가 자동으로 붙는 상태 변경 요청의 의도를 확인한다"
- listitem [ref=f16e386]: "identity header : edge가 확인한 사용자 정보의 투영이고 JWT가 아니다"
- paragraph [ref=f16e387]: 마지막 검증
- region [ref=f16e388]:
- paragraph [ref=f16e389]: Relations
- heading "이 기록과 연결된 맥락" [level=2] [ref=f16e390]
- list [ref=f16e391]:
- listitem [ref=f16e392]:
- link "JavaScript memory의 OAuth token과 Keycloak SSO session을 구분한 Case다. SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" [ref=f16e393] [cursor=pointer]:
- /url: /cases/spa-browser-credential-boundary
- generic [ref=f16e394]: JavaScript memory의 OAuth token과 Keycloak SSO session을 구분한 Case다.
- strong [ref=f16e395]: SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계
- generic [ref=f16e396]:
- listitem [ref=f16e397]:
- link "같은 요청 안에서 session cookie와 access token이 함께 움직인다. Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출" [ref=f16e398] [cursor=pointer]:
- /url: /cases/split-custody-access-token
- generic [ref=f16e399]: 같은 요청 안에서 session cookie와 access token이 함께 움직인다.
- strong [ref=f16e400]: Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출
- generic [ref=f16e401]:
- listitem [ref=f16e402]:
- link "BFF에서는 session cookie, JavaScript가 읽는 CSRF token, server-side OAuth token을 각각 다른 용도로 사용한다. Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정" [ref=f16e403] [cursor=pointer]:
- /url: /cases/bff-session-csrf-responsibility
- generic [ref=f16e404]: BFF에서는 session cookie, JavaScript가 읽는 CSRF token, server-side OAuth token을 각각 다른 용도로 사용한다.
- strong [ref=f16e405]: Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정
- generic [ref=f16e406]:
- listitem [ref=f16e407]:
- link "Forward-Auth에서는 upstream이 JWT를 직접 검증하지 않고 proxy session을 기반으로 edge가 만든 identity header를 사용한다. Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" [ref=f16e408] [cursor=pointer]:
- /url: /cases/identity-header-trust
- generic [ref=f16e409]: Forward-Auth에서는 upstream이 JWT를 직접 검증하지 않고 proxy session을 기반으로 edge가 만든 identity header를 사용한다.
- strong [ref=f16e410]: Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유
- generic [ref=f16e411]:
- complementary [ref=f16e412]:
- heading "작업 상태" [level=2] [ref=f16e413]
- status "편집 상태" [ref=f16e414]: 저장됨
- generic [ref=f16e415]:
- generic [ref=f16e416]:
- term [ref=f16e417]: 저장 버전
- definition [ref=f16e418]: "12"
- generic [ref=f16e419]:
- term [ref=f16e420]: 종류
- definition [ref=f16e421]: REFERENCE
- paragraph [ref=f16e422]: 불완전한 초안도 저장할 수 있습니다. Ctrl+S 로도 저장합니다. 게시를 누르면 채워야 할 칸을 그 자리에 표시합니다.
- generic [ref=f16e423]:
- button "저장" [disabled] [ref=f16e424]
- button "게시" [ref=f16e425]
- paragraph [ref=f16e426]: 버전 12으로 저장했습니다.