- generic [ref=f14e3]: - link "본문으로 건너뛰기" [ref=f14e4] [cursor=pointer]: - /url: "#main-content" - banner [ref=f14e5]: - generic [ref=f14e6]: - link "TechLog Studio" [ref=f14e7] [cursor=pointer]: - /url: /studio - text: TechLog - generic [ref=f14e8]: Studio - navigation "Studio 주 탐색" [ref=f14e10]: - link "작업본" [ref=f14e11] [cursor=pointer]: - /url: /studio/documents - link "게시 기록" [ref=f14e12] [cursor=pointer]: - /url: /studio/publications - link "새 문서" [ref=f14e13] [cursor=pointer]: - /url: /studio/documents/new - link "주제·프로젝트" [ref=f14e14] [cursor=pointer]: - /url: /studio/taxonomy - link "릴리즈" [ref=f14e15] [cursor=pointer]: - /url: /studio/releases - link "공개 사이트 보기" [ref=f14e16] [cursor=pointer]: - /url: / - button "로그아웃" [ref=f14e17] - main [ref=f14e18]: - generic [ref=f14e19]: - generic [ref=f14e20]: - region [ref=f14e21]: - generic [ref=f14e22]: - paragraph [ref=f14e23]: REFERENCE · VERSION 11 - heading "문서 편집" [level=1] [ref=f14e24] - paragraph [ref=f14e25]: BFF 인증 구조 설계 기준 - region [ref=f14e26]: - generic [ref=f14e27]: - paragraph [ref=f14e28]: DOCUMENT - heading "기본 정보" [level=2] [ref=f14e29] - generic [ref=f14e30]: - generic [ref=f14e31]: - generic [ref=f14e32]: 제목 - textbox "제목" [ref=f14e33]: BFF 인증 구조 설계 기준 - generic [ref=f14e34]: - generic [ref=f14e35]: slug - textbox "slug" [ref=f14e36]: - /placeholder: 비우면 제목에서 만듭니다 (영문 소문자·숫자·하이픈) - text: bff-authentication-design-criteria - generic [ref=f14e37]: - generic [ref=f14e38]: 요약 - textbox "요약" [ref=f14e39]: BFF가 OAuth token을 server-side에서 관리하고 브라우저는 session cookie로 BFF를 호출할 때 필요한 설계 항목을 정리한다. CSRF 검증, authorized client 저장소, logout, downstream 오류 처리가 핵심이다. - generic [ref=f14e40]: - generic [ref=f14e41]: Topic - combobox "Topic" [ref=f14e42]: - option "선택하지 않음" - option "OAuth/OIDC 인증 경계" [selected] - generic [ref=f14e43]: - generic [ref=f14e44]: Project - combobox "Project" [ref=f14e45]: - option "미지정" - option "Backend Clean Architecture" - option "KeyCloak Patterns" [selected] - option "Liner N + 1문제" - group "관계" [ref=f14e46]: - generic [ref=f14e48]: - generic [ref=f14e49]: - generic [ref=f14e50]: 관계 1 대상 - combobox "관계 1 대상" [ref=f14e51]: - option "대상 선택" - option "인증 구조를 보안 성숙도 단계로 취급하지 않는다" - option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가" [disabled] - option "Authorization Code Flow의 Endpoint와 Credential 이동 기준" - option "BFF 인증 구조 설계 기준" - option "BFF가 OAuth Token을 관리하는 조건" [disabled] - option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가" [disabled] - option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정" [selected] - option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가" - option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" - 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에 노출" - option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가" - option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" - generic [ref=f14e52]: - generic [ref=f14e53]: 관계 1 이유 - textbox "관계 1 이유" [ref=f14e54]: 이 기준의 항목 중 실제로 구현된 것과 비어 있는 것을 센 기록이다. - generic [ref=f14e55]: - button "위로" [disabled] [ref=f14e56] - button "아래로" [ref=f14e57] - button "삭제" [ref=f14e58] - generic [ref=f14e59]: - generic [ref=f14e60]: - generic [ref=f14e61]: 관계 2 대상 - combobox "관계 2 대상" [ref=f14e62]: - option "대상 선택" - option "인증 구조를 보안 성숙도 단계로 취급하지 않는다" - option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가" [selected] - option "Authorization Code Flow의 Endpoint와 Credential 이동 기준" - option "BFF 인증 구조 설계 기준" - option "BFF가 OAuth Token을 관리하는 조건" [disabled] - option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가" [disabled] - option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정" [disabled] - option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가" - option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" - 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에 노출" - option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가" - option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" - generic [ref=f14e63]: - generic [ref=f14e64]: 관계 2 이유 - textbox "관계 2 이유" [ref=f14e65]: 저장소 항목이 아직 답이 없는 질문으로 남아 있다. - generic [ref=f14e66]: - button "위로" [ref=f14e67] - button "아래로" [ref=f14e68] - button "삭제" [ref=f14e69] - generic [ref=f14e70]: - generic [ref=f14e71]: - generic [ref=f14e72]: 관계 3 대상 - combobox "관계 3 대상" [ref=f14e73]: - option "대상 선택" - option "인증 구조를 보안 성숙도 단계로 취급하지 않는다" - option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가" [disabled] - option "Authorization Code Flow의 Endpoint와 Credential 이동 기준" - option "BFF 인증 구조 설계 기준" - option "BFF가 OAuth Token을 관리하는 조건" [disabled] - option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가" [selected] - option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정" [disabled] - option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가" - option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" - 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에 노출" - option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가" - option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" - generic [ref=f14e74]: - generic [ref=f14e75]: 관계 3 이유 - textbox "관계 3 이유" [ref=f14e76]: 어느 저장소에 둘지가 이 기준의 미결 항목이다. - generic [ref=f14e77]: - button "위로" [ref=f14e78] - button "아래로" [ref=f14e79] - button "삭제" [ref=f14e80] - generic [ref=f14e81]: - generic [ref=f14e82]: - generic [ref=f14e83]: 관계 4 대상 - combobox "관계 4 대상" [ref=f14e84]: - option "대상 선택" - option "인증 구조를 보안 성숙도 단계로 취급하지 않는다" - option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가" [disabled] - option "Authorization Code Flow의 Endpoint와 Credential 이동 기준" - option "BFF 인증 구조 설계 기준" - option "BFF가 OAuth Token을 관리하는 조건" [selected] - option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가" [disabled] - option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정" [disabled] - option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가" - option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유" - 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에 노출" - option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가" - option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" - generic [ref=f14e85]: - generic [ref=f14e86]: 관계 4 이유 - textbox "관계 4 이유" [ref=f14e87]: 이 결정이 PROPOSED인 동안 실제 적용 기준은 이 문서다. - generic [ref=f14e88]: - button "위로" [ref=f14e89] - button "아래로" [disabled] [ref=f14e90] - button "삭제" [ref=f14e91] - button "관계 추가" [ref=f14e92] - region [ref=f14e93]: - generic [ref=f14e94]: - paragraph [ref=f14e95]: REFERENCE - heading "재사용할 기준" [level=2] [ref=f14e96] - generic [ref=f14e97]: - generic [ref=f14e98]: 목적 - textbox "목적" [ref=f14e99]: BFF 구조에서는 BFF가 authorization code를 token으로 교환하고 access token을 사용해 Resource Server를 호출한다. 따라서 session과 authorized client를 함께 관리하는 보안 구성요소로 본다. cookie가 credential이 되면 브라우저가 요청마다 자동으로 붙인다. 값을 바꾸는 요청은 사용자의 의도인지 따로 확인해야 한다. 그리고 재시작과 replica 이동을 견딜 저장소도 함께 필요해진다. 여기 있는 것은 「BFF를 쓴다」로 답이 되지 않는 항목들이다. - group "규칙" [ref=f14e100]: - generic [ref=f14e102]: - generic [ref=f14e103]: - generic [ref=f14e104]: 규칙 1 제목 - textbox "규칙 1 제목" [ref=f14e105]: 브라우저에는 OAuth token을 전달하지 않는다 - generic [ref=f14e106]: - generic [ref=f14e107]: 규칙 1 본문 - textbox "규칙 1 본문" [ref=f14e108]: "access token과 refresh token은 server-side authorized client에 보관한다. 브라우저가 token을 직접 사용할 필요가 없도록 BFF가 downstream 요청의 `Authorization` 헤더를 만든다. session cookie는 downstream으로 전달하지 않는다. BFF가 session을 애플리케이션 credential로 소비하고, Resource Server가 아는 Bearer 요청을 새로 만든다. 두 credential은 같은 요청 처리 안에 있지만 검증하는 주체가 다르다." - generic [ref=f14e109]: - button "위로" [disabled] [ref=f14e110] - button "아래로" [ref=f14e111] - button "삭제" [ref=f14e112] - generic [ref=f14e113]: - generic [ref=f14e114]: - generic [ref=f14e115]: 규칙 2 제목 - textbox "규칙 2 제목" [ref=f14e116]: cookie가 credential이면 상태 변경 요청에 CSRF 검증을 둔다 - generic [ref=f14e117]: - generic [ref=f14e118]: 규칙 2 본문 - textbox "규칙 2 본문" [ref=f14e119]: session cookie는 브라우저가 자동으로 전송하므로 상태 변경 endpoint에는 CSRF 검증을 적용한다. 현재 구성은 JavaScript가 CSRF cookie를 읽어 요청 헤더에 같은 값을 전달하는 방식을 사용한다. 노출 값과 제출 값이 다를 수 있다. 응답 본문의 token이 가려진 값이면 헤더에 넣는 값은 cookie에서 읽어야 한다. 두 값을 같다고 가정하고 구현하면 클라이언트가 그대로 403을 받는다. SameSite와 CSRF token은 역할이 다르다. SameSite는 특정 cross-site 요청에서 cookie 전송을 제한하는 브라우저 정책이고, CSRF token은 cookie가 포함된 상태 변경 요청을 서버가 추가로 검증하는 값이다. 같은 site로 계산되는 다른 origin 요청도 고려해야 한다. - generic [ref=f14e120]: - button "위로" [ref=f14e121] - button "아래로" [ref=f14e122] - button "삭제" [ref=f14e123] - generic [ref=f14e124]: - generic [ref=f14e125]: - generic [ref=f14e126]: 규칙 3 제목 - textbox "규칙 3 제목" [ref=f14e127]: session과 authorized client의 수명주기를 따로 설계한다 - generic [ref=f14e128]: - generic [ref=f14e129]: 규칙 3 본문 - textbox "규칙 3 본문" [ref=f14e130]: session은 session ID로 조회하고 authorized client는 registration 이름과 principal name으로 조회한다. shared store를 도입할 때 두 저장 구조를 각각 확인해야 한다. 같은 사용자가 두 브라우저에서 로그인하면 같은 token 항목을 공유하거나 덮어쓴다. session ID마다 token을 따로 보관해야 하면 그렇게 설계해야 한다. 저장소는 재시작과 replica 이동을 견뎌야 한다. 공유 durable store와 session affinity, 저장 token 암호화 중 무엇을 쓸지 정하고 암호화 key 교체 방법도 같이 정한다. logout에서는 application session과 authorized client를 모두 정리한다. 두 상태의 lookup key가 다르므로 삭제 처리도 각각 확인해야 한다. - generic [ref=f14e131]: - button "위로" [ref=f14e132] - button "아래로" [ref=f14e133] - button "삭제" [ref=f14e134] - generic [ref=f14e135]: - generic [ref=f14e136]: - generic [ref=f14e137]: 규칙 4 제목 - textbox "규칙 4 제목" [ref=f14e138]: downstream 오류를 화면 오류로 바꾸는 규칙을 둔다 - generic [ref=f14e139]: - generic [ref=f14e140]: 규칙 4 본문 - textbox "규칙 4 본문" [ref=f14e141]: Resource Server의 401을 그대로 내려보내면 사용자는 로그인이 끊긴 것인지 권한이 없는 것인지 알 수 없다. timeout과 retry, circuit breaker, 재로그인 전환도 함께 정한다. 모든 UI 요청이 BFF를 지나기 때문에 여기서 정하지 않으면 화면마다 다르게 처리된다. - generic [ref=f14e142]: - button "위로" [ref=f14e143] - button "아래로" [ref=f14e144] - button "삭제" [ref=f14e145] - generic [ref=f14e146]: - generic [ref=f14e147]: - generic [ref=f14e148]: 규칙 5 제목 - textbox "규칙 5 제목" [ref=f14e149]: 자기 보고 값을 증거로 쓰지 않는다 - generic [ref=f14e150]: - generic [ref=f14e151]: 규칙 5 본문 - textbox "규칙 5 본문" [ref=f14e152]: 「브라우저에 token이 없다」고 서버가 응답에 적는 값은 서버가 넣은 상수다. 브라우저를 들여다본 결과가 아니다. 진단 endpoint의 응답과 별개로 브라우저 개발자 도구에서 network 요청과 Web Storage를 직접 확인한다. 애플리케이션이 스스로 보고한 값과 브라우저에서 관측한 결과를 구분해 기록한다. - generic [ref=f14e153]: - button "위로" [ref=f14e154] - button "아래로" [ref=f14e155] - button "삭제" [ref=f14e156] - generic [ref=f14e157]: - generic [ref=f14e158]: - generic [ref=f14e159]: 규칙 6 제목 - textbox "규칙 6 제목" [ref=f14e160]: BFF에서도 XSS 방어는 별도로 필요하다 - generic [ref=f14e161]: - generic [ref=f14e162]: 규칙 6 본문 - textbox "규칙 6 본문" [ref=f14e163]: same-origin에서 악성 script가 실행되면 피해자 session으로 BFF endpoint를 호출하고 JavaScript에서 읽을 수 있는 CSRF cookie에도 접근할 수 있다. BFF는 OAuth token 원문을 브라우저 JavaScript에 전달하지 않지만, CSP와 output encoding, 의존성 무결성, 애플리케이션 인가는 별도로 적용해야 한다. - generic [ref=f14e164]: - button "위로" [ref=f14e165] - button "아래로" [disabled] [ref=f14e166] - button "삭제" [ref=f14e167] - button "규칙 추가" [ref=f14e168] - group "적용 조건" [ref=f14e169]: - generic [ref=f14e171]: - generic [ref=f14e172]: - generic [ref=f14e173]: 적용 조건 1 - textbox "적용 조건 1" [ref=f14e174]: 브라우저가 OAuth token을 받아서는 안 될 때 - generic [ref=f14e175]: - button "위로" [disabled] [ref=f14e176] - button "아래로" [ref=f14e177] - button "삭제" [ref=f14e178] - generic [ref=f14e179]: - generic [ref=f14e180]: - generic [ref=f14e181]: 적용 조건 2 - textbox "적용 조건 2" [ref=f14e182]: backend가 화면에 맞춰 여러 API를 조합해야 할 때 - generic [ref=f14e183]: - button "위로" [ref=f14e184] - button "아래로" [ref=f14e185] - button "삭제" [ref=f14e186] - generic [ref=f14e187]: - generic [ref=f14e188]: - generic [ref=f14e189]: 적용 조건 3 - textbox "적용 조건 3" [ref=f14e190]: 로그인 상태를 애플리케이션이 소유해야 할 때 - generic [ref=f14e191]: - button "위로" [ref=f14e192] - button "아래로" [ref=f14e193] - button "삭제" [ref=f14e194] - generic [ref=f14e195]: - generic [ref=f14e196]: - generic [ref=f14e197]: 적용 조건 4 - textbox "적용 조건 4" [ref=f14e198]: downstream API가 늘어나도 브라우저는 하나만 알게 하고 싶을 때 - generic [ref=f14e199]: - button "위로" [ref=f14e200] - button "아래로" [disabled] [ref=f14e201] - button "삭제" [ref=f14e202] - button "적용 조건 추가" [ref=f14e203] - group "예외" [ref=f14e204]: - generic [ref=f14e206]: - generic [ref=f14e207]: - generic [ref=f14e208]: 예외 1 - textbox "예외 1" [ref=f14e209]: stateless 직접 API 호출과 독립 client가 핵심이면 BFF를 넣지 않는다. server state와 단일 장애 지점만 늘어난다. - generic [ref=f14e210]: - button "위로" [disabled] [ref=f14e211] - button "아래로" [ref=f14e212] - button "삭제" [ref=f14e213] - generic [ref=f14e214]: - generic [ref=f14e215]: - generic [ref=f14e216]: 예외 2 - textbox "예외 2" [ref=f14e217]: 브라우저의 직접 API 호출을 남겨야 하면 refresh credential만 서버로 분리하는 구조가 맞다. - generic [ref=f14e218]: - button "위로" [ref=f14e219] - button "아래로" [ref=f14e220] - button "삭제" [ref=f14e221] - generic [ref=f14e222]: - generic [ref=f14e223]: - generic [ref=f14e224]: 예외 3 - textbox "예외 3" [ref=f14e225]: server state를 둘 수 없는 환경이면 브라우저가 token을 직접 다루는 구조가 더 단순하다. - generic [ref=f14e226]: - button "위로" [ref=f14e227] - button "아래로" [disabled] [ref=f14e228] - button "삭제" [ref=f14e229] - button "예외 추가" [ref=f14e230] - group "예시" [ref=f14e231]: - generic [ref=f14e233]: - generic [ref=f14e234]: - generic [ref=f14e235]: 예시 1 - textbox "예시 1" [ref=f14e236]: 브라우저 요청에는 Authorization 헤더가 없고 session cookie만 있다 - generic [ref=f14e237]: - button "위로" [disabled] [ref=f14e238] - button "아래로" [ref=f14e239] - button "삭제" [ref=f14e240] - generic [ref=f14e241]: - generic [ref=f14e242]: - generic [ref=f14e243]: 예시 2 - textbox "예시 2" [ref=f14e244]: BFF가 authorized client에서 access token을 읽어 downstream Bearer 요청을 새로 만든다 - generic [ref=f14e245]: - button "위로" [ref=f14e246] - button "아래로" [ref=f14e247] - button "삭제" [ref=f14e248] - generic [ref=f14e249]: - generic [ref=f14e250]: - generic [ref=f14e251]: 예시 3 - textbox "예시 3" [ref=f14e252]: CSRF 헤더가 없는 POST는 403이 되고 cookie의 raw 값을 헤더에 넣은 POST는 200이 된다 - generic [ref=f14e253]: - button "위로" [ref=f14e254] - button "아래로" [ref=f14e255] - button "삭제" [ref=f14e256] - generic [ref=f14e257]: - generic [ref=f14e258]: - generic [ref=f14e259]: 예시 4 - textbox "예시 4" [ref=f14e260]: 응답 본문의 token은 가려진 값이고 헤더에 넣는 값은 cookie의 raw 값이다 - generic [ref=f14e261]: - button "위로" [ref=f14e262] - button "아래로" [ref=f14e263] - button "삭제" [ref=f14e264] - generic [ref=f14e265]: - generic [ref=f14e266]: - generic [ref=f14e267]: 예시 5 - textbox "예시 5" [ref=f14e268]: 진단 endpoint의 browserTokenCount는 controller literal이라서 token 비노출의 근거가 아니다 - generic [ref=f14e269]: - button "위로" [ref=f14e270] - button "아래로" [disabled] [ref=f14e271] - button "삭제" [ref=f14e272] - button "예시 추가" [ref=f14e273] - generic [ref=f14e274]: - generic [ref=f14e275]: 마지막 검증일 - textbox "마지막 검증일" [ref=f14e276] - region [ref=f14e277]: - generic [ref=f14e278]: - paragraph [ref=f14e279]: LIVE - heading "즉시 미리보기" [level=2] [ref=f14e280] - generic [ref=f14e283]: - generic [ref=f14e284]: - navigation "문서 경로" [ref=f14e285]: - link "Reference" [ref=f14e286] [cursor=pointer]: - /url: /explore/references - generic [ref=f14e287]: / - generic [ref=f14e288]: OAuth/OIDC 인증 경계 - generic [ref=f14e289]: / - link "KeyCloak Patterns" [ref=f14e290] [cursor=pointer]: - /url: /projects/keycloak-patterns - heading "BFF 인증 구조 설계 기준" [level=1] [ref=f14e291] - paragraph [ref=f14e292]: BFF가 OAuth token을 server-side에서 관리하고 브라우저는 session cookie로 BFF를 호출할 때 필요한 설계 항목을 정리한다. CSRF 검증, authorized client 저장소, logout, downstream 오류 처리가 핵심이다. - generic [ref=f14e293]: - generic [ref=f14e294]: - term [ref=f14e295]: 유형 - definition [ref=f14e296]: Reference - generic [ref=f14e297]: - term [ref=f14e298]: 프로젝트 - definition [ref=f14e299]: KeyCloak Patterns - generic [ref=f14e300]: - term [ref=f14e301]: 게시 - definition [ref=f14e302]: 게시 전 - region [ref=f14e303]: - paragraph [ref=f14e304]: Purpose - heading "이 기준을 쓰는 이유" [level=2] [ref=f14e305] - paragraph [ref=f14e306]: BFF 구조에서는 BFF가 authorization code를 token으로 교환하고 access token을 사용해 Resource Server를 호출한다. 따라서 session과 authorized client를 함께 관리하는 보안 구성요소로 본다.cookie가 credential이 되면 브라우저가 요청마다 자동으로 붙인다. 값을 바꾸는 요청은 사용자의 의도인지 따로 확인해야 한다. 그리고 재시작과 replica 이동을 견딜 저장소도 함께 필요해진다.여기 있는 것은 「BFF를 쓴다」로 답이 되지 않는 항목들이다. - article [ref=f14e307]: - region [ref=f14e308]: - heading "판단 기준" [level=2] [ref=f14e309] - list [ref=f14e310]: - listitem [ref=f14e311]: - generic [ref=f14e312]: "01" - generic [ref=f14e313]: - heading "브라우저에는 OAuth token을 전달하지 않는다" [level=3] [ref=f14e314] - paragraph [ref=f14e315]: "access token과 refresh token은 server-side authorized client에 보관한다. 브라우저가 token을 직접 사용할 필요가 없도록 BFF가 downstream 요청의 `Authorization` 헤더를 만든다. session cookie는 downstream으로 전달하지 않는다. BFF가 session을 애플리케이션 credential로 소비하고, Resource Server가 아는 Bearer 요청을 새로 만든다. 두 credential은 같은 요청 처리 안에 있지만 검증하는 주체가 다르다." - listitem [ref=f14e316]: - generic [ref=f14e317]: "02" - generic [ref=f14e318]: - heading "cookie가 credential이면 상태 변경 요청에 CSRF 검증을 둔다" [level=3] [ref=f14e319] - paragraph [ref=f14e320]: session cookie는 브라우저가 자동으로 전송하므로 상태 변경 endpoint에는 CSRF 검증을 적용한다. 현재 구성은 JavaScript가 CSRF cookie를 읽어 요청 헤더에 같은 값을 전달하는 방식을 사용한다. 노출 값과 제출 값이 다를 수 있다. 응답 본문의 token이 가려진 값이면 헤더에 넣는 값은 cookie에서 읽어야 한다. 두 값을 같다고 가정하고 구현하면 클라이언트가 그대로 403을 받는다. SameSite와 CSRF token은 역할이 다르다. SameSite는 특정 cross-site 요청에서 cookie 전송을 제한하는 브라우저 정책이고, CSRF token은 cookie가 포함된 상태 변경 요청을 서버가 추가로 검증하는 값이다. 같은 site로 계산되는 다른 origin 요청도 고려해야 한다. - listitem [ref=f14e321]: - generic [ref=f14e322]: "03" - generic [ref=f14e323]: - heading "session과 authorized client의 수명주기를 따로 설계한다" [level=3] [ref=f14e324] - paragraph [ref=f14e325]: session은 session ID로 조회하고 authorized client는 registration 이름과 principal name으로 조회한다. shared store를 도입할 때 두 저장 구조를 각각 확인해야 한다. 같은 사용자가 두 브라우저에서 로그인하면 같은 token 항목을 공유하거나 덮어쓴다. session ID마다 token을 따로 보관해야 하면 그렇게 설계해야 한다. 저장소는 재시작과 replica 이동을 견뎌야 한다. 공유 durable store와 session affinity, 저장 token 암호화 중 무엇을 쓸지 정하고 암호화 key 교체 방법도 같이 정한다. logout에서는 application session과 authorized client를 모두 정리한다. 두 상태의 lookup key가 다르므로 삭제 처리도 각각 확인해야 한다. - listitem [ref=f14e326]: - generic [ref=f14e327]: "04" - generic [ref=f14e328]: - heading "downstream 오류를 화면 오류로 바꾸는 규칙을 둔다" [level=3] [ref=f14e329] - paragraph [ref=f14e330]: Resource Server의 401을 그대로 내려보내면 사용자는 로그인이 끊긴 것인지 권한이 없는 것인지 알 수 없다. timeout과 retry, circuit breaker, 재로그인 전환도 함께 정한다. 모든 UI 요청이 BFF를 지나기 때문에 여기서 정하지 않으면 화면마다 다르게 처리된다. - listitem [ref=f14e331]: - generic [ref=f14e332]: "05" - generic [ref=f14e333]: - heading "자기 보고 값을 증거로 쓰지 않는다" [level=3] [ref=f14e334] - paragraph [ref=f14e335]: 「브라우저에 token이 없다」고 서버가 응답에 적는 값은 서버가 넣은 상수다. 브라우저를 들여다본 결과가 아니다. 진단 endpoint의 응답과 별개로 브라우저 개발자 도구에서 network 요청과 Web Storage를 직접 확인한다. 애플리케이션이 스스로 보고한 값과 브라우저에서 관측한 결과를 구분해 기록한다. - listitem [ref=f14e336]: - generic [ref=f14e337]: "06" - generic [ref=f14e338]: - heading "BFF에서도 XSS 방어는 별도로 필요하다" [level=3] [ref=f14e339] - paragraph [ref=f14e340]: same-origin에서 악성 script가 실행되면 피해자 session으로 BFF endpoint를 호출하고 JavaScript에서 읽을 수 있는 CSRF cookie에도 접근할 수 있다. BFF는 OAuth token 원문을 브라우저 JavaScript에 전달하지 않지만, CSP와 output encoding, 의존성 무결성, 애플리케이션 인가는 별도로 적용해야 한다. - region [ref=f14e341]: - heading "적용할 때" [level=2] [ref=f14e342] - list [ref=f14e343]: - listitem [ref=f14e344]: 브라우저가 OAuth token을 받아서는 안 될 때 - listitem [ref=f14e345]: backend가 화면에 맞춰 여러 API를 조합해야 할 때 - listitem [ref=f14e346]: 로그인 상태를 애플리케이션이 소유해야 할 때 - listitem [ref=f14e347]: downstream API가 늘어나도 브라우저는 하나만 알게 하고 싶을 때 - region [ref=f14e348]: - heading "예외와 주의" [level=2] [ref=f14e349] - list [ref=f14e350]: - listitem [ref=f14e351]: stateless 직접 API 호출과 독립 client가 핵심이면 BFF를 넣지 않는다. server state와 단일 장애 지점만 늘어난다. - listitem [ref=f14e352]: 브라우저의 직접 API 호출을 남겨야 하면 refresh credential만 서버로 분리하는 구조가 맞다. - listitem [ref=f14e353]: server state를 둘 수 없는 환경이면 브라우저가 token을 직접 다루는 구조가 더 단순하다. - region [ref=f14e354]: - heading "예시" [level=2] [ref=f14e355] - list [ref=f14e356]: - listitem [ref=f14e357]: 브라우저 요청에는 Authorization 헤더가 없고 session cookie만 있다 - listitem [ref=f14e358]: BFF가 authorized client에서 access token을 읽어 downstream Bearer 요청을 새로 만든다 - listitem [ref=f14e359]: CSRF 헤더가 없는 POST는 403이 되고 cookie의 raw 값을 헤더에 넣은 POST는 200이 된다 - listitem [ref=f14e360]: 응답 본문의 token은 가려진 값이고 헤더에 넣는 값은 cookie의 raw 값이다 - listitem [ref=f14e361]: 진단 endpoint의 browserTokenCount는 controller literal이라서 token 비노출의 근거가 아니다 - paragraph [ref=f14e362]: 마지막 검증 - region [ref=f14e363]: - paragraph [ref=f14e364]: Relations - heading "이 기록과 연결된 맥락" [level=2] [ref=f14e365] - list [ref=f14e366]: - listitem [ref=f14e367]: - link "이 기준의 항목 중 실제로 구현된 것과 비어 있는 것을 센 기록이다. Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정" [ref=f14e368] [cursor=pointer]: - /url: /cases/bff-session-csrf-responsibility - generic [ref=f14e369]: 이 기준의 항목 중 실제로 구현된 것과 비어 있는 것을 센 기록이다. - strong [ref=f14e370]: Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정 - generic [ref=f14e371]: ↗ - complementary [ref=f14e372]: - heading "작업 상태" [level=2] [ref=f14e373] - status "편집 상태" [ref=f14e374]: 저장됨 - generic [ref=f14e375]: - generic [ref=f14e376]: - term [ref=f14e377]: 저장 버전 - definition [ref=f14e378]: "11" - generic [ref=f14e379]: - term [ref=f14e380]: 종류 - definition [ref=f14e381]: REFERENCE - paragraph [ref=f14e382]: 불완전한 초안도 저장할 수 있습니다. Ctrl+S 로도 저장합니다. 게시를 누르면 채워야 할 칸을 그 자리에 표시합니다. - generic [ref=f14e383]: - button "저장" [disabled] [ref=f14e384] - button "게시" [ref=f14e385] - paragraph [ref=f14e386]: 버전 11으로 저장했습니다.