221 lines
20 KiB
Markdown
221 lines
20 KiB
Markdown
---
|
|
title: branch / feature-keycloak-bff-csrf-samesite-defense
|
|
source_type: branch-note
|
|
status: raw
|
|
id: BR-KEYCLOAK-PATTERNS-OVERVIEW-011
|
|
kind: project-work-item
|
|
project: keycloak-patterns-overview
|
|
work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-011
|
|
inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1, DEC-KEYCLOAK-PATTERNS-OVERVIEW-BFF-SESSION-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: [WI-KEYCLOAK-PATTERNS-OVERVIEW-010]
|
|
imports: []
|
|
delegates: []
|
|
accepts_delegations: []
|
|
contract_packet: 1
|
|
contract_packet_sha256: 00ce3ab254b077628852e877be348df58e6c0d84b03466a875ecc7a3c4023e11
|
|
branch: feature-keycloak-bff-csrf-samesite-defense
|
|
parent_branch:
|
|
related_projects: [keycloak-patterns-overview]
|
|
tags: [branch]
|
|
created: 2026-07-23
|
|
target_merge:
|
|
status_label: in-progress
|
|
---
|
|
|
|
# branch: feature-keycloak-bff-csrf-samesite-defense
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
- [[raw/project-notes/keycloak-patterns-overview]]
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: CSRF를 재현하고 SameSite와 CSRF token 적용 후 403을 검증한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| Decision Ref | Project Summary | Branch Application | Source |
|
|
|---|---|---|---|
|
|
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | done-bar는 E2E success와 signature security failure 재현·해결 evidence다 | `WI-KEYCLOAK-PATTERNS-OVERVIEW-011` 완료 조건에 적용 | `[[raw/project-notes/keycloak-patterns-overview]]` |
|
|
| `DEC-KEYCLOAK-PATTERNS-OVERVIEW-BFF-SESSION-001@1` | AP3는 token을 backend session에 두고 browser에는 cookie만 둔다 | `WI-KEYCLOAK-PATTERNS-OVERVIEW-011` 완료 조건에 적용 | `[[raw/project-notes/keycloak-patterns-overview]]` |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- GENERATED: artifact-imports:start -->
|
|
### 가져온 artifact 계약
|
|
|
|
| Artifact Ref | Owner | Producer | Schema Ref |
|
|
|---|---|---|---|
|
|
<!-- GENERATED: artifact-imports:end -->
|
|
|
|
<!-- GENERATED: project-contract-imports:start -->
|
|
## 가져온 프로젝트 계약
|
|
|
|
| Ref | Owner | 요약 | Branch 적용 |
|
|
|---|---|---|---|
|
|
<!-- GENERATED: project-contract-imports:end -->
|
|
|
|
<!-- GENERATED: received-delegations:start -->
|
|
### 수신한 위임
|
|
|
|
| Delegation Ref | From | Concern | Status |
|
|
|---|---|---|---|
|
|
<!-- GENERATED: received-delegations:end -->
|
|
|
|
<!-- GENERATED: flow:start -->
|
|
### 가져온 흐름 단계
|
|
|
|
| Stage Ref | Order | Owner | Input | Action | Output |
|
|
|---|---:|---|---|---|---|
|
|
<!-- GENERATED: flow:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
- `WI-KEYCLOAK-PATTERNS-OVERVIEW-011`의 완료 조건을 구현한다: CSRF를 재현하고 SameSite와 CSRF token 적용 후 403을 검증한다
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- AP3(BFF) cookie-session 이 **CSRF 에 노출**됨을 재현(state-changing 요청을 토큰 없이 위조).
|
|
- **Spring Security CSRF token**(synchronizer token pattern) 적용 후 토큰 없는 위조 요청이 `403` 으로 차단됨을 검증.
|
|
- **SameSite 쿠키 속성**(SESSION 쿠키에 `Lax`)을 defense-in-depth 로 결합(완료조건이 요구) — CSRF token(D2)의 대체가 아닌 보완. 근거: MDN·RFC 6265bis·OWASP·Spring Boot(D3, 2026-07-25 아카이빙 완료).
|
|
|
|
### 제외 범위
|
|
|
|
> 의도적으로 제외. 면접 등에서 "이건 범위에 없었습니다" 근거.
|
|
|
|
- BFF `oauth2Login` 세션 수립 자체 → `WI-010`(`feature-keycloak-bff-oauth2login-session`) 소유. 본 branch 는 그 세션의 CSRF 방어만.
|
|
- token 을 browser 에 노출하는 패턴(AP1/AP2) — 본 branch 는 AP3(browser 는 session cookie 만) 전제.
|
|
- project decision registry 변경.
|
|
|
|
## 근거 (필수, 최소 1개+)
|
|
|
|
| Source | 정당화하는 결정 |
|
|
|---|---|
|
|
| `[[raw/official-docs/csrf-protection-spring-official]]` | D1·D2 — Spring Security 는 unsafe method 에 기본 CSRF 방어(`SPRINGSEC-CSRF-C1`), 토큰 없는 위조 요청 가능(`C2`), synchronizer token pattern(`C3`), `CookieCsrfTokenRepository`=`XSRF-TOKEN`/`X-XSRF-TOKEN`(`C4`), BREACH 방어(`C5`) |
|
|
| `[[raw/project-notes/keycloak-patterns-overview]]` | 상속 — `DEC-…-BFF-SESSION-001@1`(AP3=session·browser cookie 만), `DEC-…-ACCEPTANCE-001@1`(재현→해결 evidence) |
|
|
| `[[raw/official-docs/rfc6265bis-samesite-attribute-ietf]]` | D3 — SameSite 세 값(Strict/Lax/None) 표준 정의(`RFC6265BIS-SAMESITE-C1/C2/C3`), `Lax` 가 cross-site top-level navigation(safe method)에 쿠키를 허용하는 rationale(`C4`), 단 `Lax` 가 top-level **POST** 콜백에는 부적합함을 명시하는 경계(`C5`) — AP3 콜백이 GET/safe-method top-level navigation 인지 확인 후 D3 확정 필요(IETF Internet-Draft, 확정 RFC 아님) |
|
|
| `[[raw/official-docs/samesite-set-cookie-mdn-official]]` · `[[raw/official-docs/spring-boot-session-cookie-samesite-property-official]]` · `[[raw/official-docs/spring-boot-cookie-samesite-enum-javadoc-official]]` | D3 — MDN SameSite 서술(`MDN-SAMESITE-C1~C4`) + Spring Boot 설정 프로퍼티 `server.servlet.session.cookie.same-site`(`SPRINGBOOT-SESSION-SAMESITE-C1`). 프로퍼티 페이지가 허용값을 미열거하므로 `Cookie.SameSite` enum Javadoc 으로 리터럴 값(`LAX`=`SPRINGBOOT-COOKIE-SAMESITE-ENUM-C4`, STRICT/NONE/OMITTED=`C5/C3/C2`)을 보강 |
|
|
| `[[raw/official-docs/csrf-prevention-cheat-sheet-owasp-samesite-official]]` | D3 — OWASP CSRF cheat-sheet 는 SameSite 를 **defense-in-depth 통제**로 명시하고 "does not replace a proper CSRF defense"(`OWASP-CSRF-SAMESITE-C1`)라 규정 — D2(CSRF token, synchronizer pattern)와 D3(SameSite)를 **결합(combine, 대체 아님)**으로 프레이밍하는 근거(`C5`: synchronizer token pattern 이 1차 권장 방법). 세션 쿠키 적용 권고(`C2`), Lax 균형/Strict UX 비용 trade-off(`C3`·`C4`)도 포함. 앞 행의 "OWASP CSRF cheat-sheet 미아카이빙(deferred)" 항목은 이 아카이빙으로 해소됨 |
|
|
|
|
## TODO
|
|
|
|
- [ ] BFF cookie-session 에 대한 CSRF 위조 요청 재현(토큰 없이 state-changing 성공) — 등급: `planned`
|
|
- [ ] Spring Security CSRF token 적용 → 토큰 없는 위조 `403` 검증 — 등급: `planned`
|
|
- [ ] SameSite=Lax 를 SESSION 쿠키에 적용(`server.servlet.session.cookie.same-site=lax`) — 근거 소스 확보 완료(MDN·RFC 6265bis·OWASP·Spring Boot). 착수 시 콜백 method(GET vs `form_post`) 확인 — 등급: `planned`
|
|
|
|
## 진행 중 메모
|
|
|
|
- CSRF 토큰 근거(`csrf-protection-spring-official`) 확보. **SameSite 근거도 확보 완료** (2026-07-25 아카이빙: MDN·RFC 6265bis(IETF Internet-Draft)·OWASP CSRF cheat-sheet·Spring Boot `server.servlet.session.cookie.same-site`) → D3 는 `UNSUPPORTED_DECISION` 에서 **근거 있는 결정**(SESSION 쿠키에 `SameSite=Lax`, CSRF token 과 병행)으로 승격.
|
|
- 남은 open risk(결정은 성립, 착수 시 검증): (1) AP3 Keycloak 콜백이 GET/safe-method top-level navigation 인지 — `response_mode=form_post`(POST)면 Lax 가 쿠키를 제외해 로그인 흐름 실패(`RFC6265BIS-SAMESITE-C5`). (2) `XSRF-TOKEN` 쿠키의 SameSite 설정 API 는 공식 근거 미확보 → `UNSUPPORTED_IMPL_DECISION` 유지(SameSite 1차 대상은 SESSION 쿠키). (3) Spring Session(Redis 등) 사용 시 위 프로퍼티가 무시될 수 있음(vendor 이슈, 미검증) — 세션 구현체(`DEC-…-BFF-SESSION-001`) 확인 선행.
|
|
- AP3 코드 미구현(`NO_GROUND_TRUTH`) → 검증 등급은 여전히 전부 `planned`.
|
|
|
|
## 결정 사항
|
|
|
|
- 2026-07-24: **D1** BFF cookie-session 의 CSRF 노출을 먼저 *재현*(토큰 없는 위조 state-changing 요청) / 이유: acceptance 계약(재현→해결 evidence) / 대안: 재현 생략하고 방어만(evidence 약화) / 근거: `[[raw/official-docs/csrf-protection-spring-official]]`(`SPRINGSEC-CSRF-C2`)
|
|
- 2026-07-24: **D2** Spring Security CSRF token(synchronizer token pattern, 기본 활성 + `CookieCsrfTokenRepository`) 적용 → `403` / 이유: 공식 기본 방어, JS 클라이언트엔 `XSRF-TOKEN` 쿠키 노출 필요 / 대안: double-submit only / 근거: `[[raw/official-docs/csrf-protection-spring-official]]`(`C1`·`C3`·`C4`·`C5`)
|
|
- 2026-07-25: **D3** (2026-07-24 `UNSUPPORTED_DECISION` → 근거 확보로 승격) SESSION 쿠키에 `SameSite=Lax` 적용(`server.servlet.session.cookie.same-site=lax`)을 CSRF token(D2)과 병행하는 defense-in-depth / 이유: OWASP 는 SameSite 를 "does not replace a proper CSRF defense"(`OWASP-CSRF-SAMESITE-C1`)인 보완 통제로 규정하고 synchronizer token 을 1차 권장(`OWASP-CSRF-SAMESITE-C5`); RFC 6265bis 는 `Lax` 를 top-level navigation(safe method)을 깨지 않으면서 CSRF 를 완화하는 drop-in 으로 정의(`RFC6265BIS-SAMESITE-C2`·`C4`), 외부 IdP(Keycloak) 로그인 redirect(cross-site top-level GET navigation)와 호환(`MDN-SAMESITE-C2`); OWASP 는 외부 링크 세션 유지에 Lax 가 합리적 균형이라 명시(`OWASP-CSRF-SAMESITE-C3`); 설정은 Spring Boot `server.servlet.session.cookie.same-site`(`SPRINGBOOT-SESSION-SAMESITE-C1`) / 검토한 대안: **Strict**(cross-site 전면 차단이나 Keycloak 콜백에 세션 쿠키 미전송 → 로그인 흐름 파괴 위험 `RFC6265BIS-SAMESITE-C5`·`OWASP-CSRF-SAMESITE-C4`; 순수 same-site 내부 로그인일 때만 적합), **None**(CSRF 방어 기여 0 + `Secure` 강제 `MDN-SAMESITE-C4` — 배제), **CSRF token 단독**(SameSite 보완 없음, 완료조건의 SameSite 요구 미충족) / 근거: `[[raw/official-docs/rfc6265bis-samesite-attribute-ietf]]`·`[[raw/official-docs/samesite-set-cookie-mdn-official]]`·`[[raw/official-docs/csrf-prevention-cheat-sheet-owasp-samesite-official]]`·`[[raw/official-docs/spring-boot-session-cookie-samesite-property-official]]`
|
|
|
|
<!-- section-id: decision-evidence -->
|
|
## Decision Evidence Map / 결정-근거 매핑
|
|
|
|
> D1·D2 는 `official-vendor-doc`(Spring). D3(SameSite)는 2026-07-25 근거 확보 → `official-standard`(RFC 6265bis, IETF Internet-Draft) + `official-reference`(MDN·OWASP) + `official-vendor-doc`(Spring Boot). Curity 등 company-case-study 단독 official 단언 금지.
|
|
|
|
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|
|
|---|---|---|---|---|---|
|
|
| D1 | BFF cookie-session CSRF 노출을 재현(토큰 없는 위조 state-changing) | acceptance(재현→해결) → 이 결정 / 재현 생략 → evidence 약화 | `raw/official-docs/csrf-protection-spring-official.md#SPRINGSEC-CSRF-C2` | `official-vendor-doc` | C2 는 logout 시나리오 한정 결과 진술 — 일반 state-changing 위조 재현 절차는 구현 상세 |
|
|
| D2 | Spring Security CSRF token(synchronizer + `CookieCsrfTokenRepository` + BREACH) 적용 → `403` | AP3 JS 클라이언트 → 이 결정(쿠키로 토큰 노출) / server-rendered → form hidden field | `raw/official-docs/csrf-protection-spring-official.md#SPRINGSEC-CSRF-C1`, `#SPRINGSEC-CSRF-C3`, `#SPRINGSEC-CSRF-C4`, `#SPRINGSEC-CSRF-C5` | `official-vendor-doc` | `XSRF-TOKEN` 쿠키의 SameSite 기본값/설정은 이 소스 범위 밖(C4 Open Risk) → D3 와 연결 |
|
|
| D3 | SESSION 쿠키에 `SameSite=Lax` 적용(`server.servlet.session.cookie.same-site=lax`)을 CSRF token(D2)과 병행 — defense-in-depth(대체 아님) | 외부 IdP(Keycloak) cross-site top-level GET 콜백 존재 → `Lax` / 순수 same-site 내부 로그인만 → `Strict` 재검토 / cross-site 임베드 요구 → `None`(+Secure), 본 branch 해당 없음 | `raw/official-docs/rfc6265bis-samesite-attribute-ietf.md#RFC6265BIS-SAMESITE-C2`, `#RFC6265BIS-SAMESITE-C4`, `raw/official-docs/samesite-set-cookie-mdn-official.md#MDN-SAMESITE-C2`, `raw/official-docs/csrf-prevention-cheat-sheet-owasp-samesite-official.md#OWASP-CSRF-SAMESITE-C1`, `#OWASP-CSRF-SAMESITE-C3`, `#OWASP-CSRF-SAMESITE-C5`, `raw/official-docs/spring-boot-session-cookie-samesite-property-official.md#SPRINGBOOT-SESSION-SAMESITE-C1`, `raw/official-docs/spring-boot-cookie-samesite-enum-javadoc-official.md#SPRINGBOOT-COOKIE-SAMESITE-ENUM-C4` | `official-standard + official-reference + official-vendor-doc` | (1) 콜백이 top-level **POST**(`response_mode=form_post`)면 Lax 가 쿠키 제외 → 로그인 실패(`RFC6265BIS-SAMESITE-C5`); GET 콜백 확인 필요. (2) `XSRF-TOKEN` 쿠키 SameSite 설정 API 근거 없음 → `UNSUPPORTED_IMPL_DECISION`. (3) Spring Session 사용 시 프로퍼티 무시 가능(미검증) — 세션 구현체 확인 선행. (4) 리터럴 허용값(`LAX`/`STRICT`/`NONE`/`OMITTED`)은 `Cookie.SameSite` enum Javadoc(`SPRINGBOOT-COOKIE-SAMESITE-ENUM-C2~C5`)으로 확보 — 프로퍼티 기본값 자체는 여전히 미열거 |
|
|
| D4 | 완료 판정 = CSRF 재현 AND (SameSite + CSRF token) 적용 후 `403` (상속 acceptance) | 항상 / N/A | `raw/project-notes/keycloak-patterns-overview.md` (`DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1`, 상속) | `project-decision(inherited)` | D3 근거 확보 완료 → 완료조건의 "SameSite" 부분 충족 가능. 남은 것은 코드 구현 + 콜백 method 검증(D3 Open Risk 1) |
|
|
|
|
<!-- section-id: implementation -->
|
|
## 구현 가이드
|
|
|
|
> **Trace**: D1(`SPRINGSEC-CSRF-C2`) + D2(`SPRINGSEC-CSRF-C1/C3/C4/C5`) + D3(`RFC6265BIS-SAMESITE-C2/C4/C5`·`MDN-SAMESITE-C2`·`OWASP-CSRF-SAMESITE-C1/C3/C5`·`SPRINGBOOT-SESSION-SAMESITE-C1`·`SPRINGBOOT-COOKIE-SAMESITE-ENUM-C4`) + D4(inherited). AP3 코드 부재(`NO_GROUND_TRUTH`)라 사전 명세, 등급 `planned`.
|
|
>
|
|
> - SESSION 쿠키 SameSite 는 이제 **근거 있는 결정**(`Lax`, `server.servlet.session.cookie.same-site=lax`, D3) — UNSUPPORTED 아님.
|
|
> - **UNSUPPORTED_IMPL_DECISION**: (a) **`XSRF-TOKEN` 쿠키의 SameSite 설정** — `CookieCsrfTokenRepository` 쿠키에 SameSite 를 지정하는 공식 API 근거 미확보 → 착수 시 결정. trade-off: SameSite 1차 대상은 실제 인증 상태를 쥔 SESSION 쿠키이므로 SESSION 만 Lax 로도 완료조건 충족 vs XSRF-TOKEN 까지 맞추면 일관성↑(근거 소스 필요). (b) **CSRF 재현 벡터** — cross-site auto-submit form / fetch(credentials:'include'). 소스는 위조 가능성만 진술.
|
|
|
|
| 항목 | 사전 명세 (planned) | Trace |
|
|
|---|---|---|
|
|
| CSRF 재현 | cross-site 에서 cookie 자동첨부로 state-changing POST 위조 성공(토큰 없이) | D1(`SPRINGSEC-CSRF-C2`) · UNSUPPORTED_IMPL_DECISION(b) |
|
|
| CSRF token | `.csrf()` 기본 + `CookieCsrfTokenRepository.withHttpOnlyFalse()`(vanilla-JS SPA 가 `XSRF-TOKEN` 쿠키를 읽어 `X-XSRF-TOKEN` 헤더로 실어야 하므로 httpOnly off — 소스 §적용경계 코드 예제) + 기본 BREACH(`XorCsrfTokenRequestAttributeHandler`) → 토큰 없는 위조 `403` | D2(`SPRINGSEC-CSRF-C1/C3/C4/C5`); `withHttpOnlyFalse()` 는 C4 범위 밖 API detail(소스 코드 예제 근거) |
|
|
| SameSite (SESSION 쿠키) | `server.servlet.session.cookie.same-site=lax`(리터럴 `lax` = enum `LAX`) — cross-site subrequest(CSRF 벡터) 차단 + Keycloak top-level GET 콜백 유지 | D3(`SPRINGBOOT-SESSION-SAMESITE-C1`·`SPRINGBOOT-COOKIE-SAMESITE-ENUM-C4`·`RFC6265BIS-SAMESITE-C2`·`OWASP-CSRF-SAMESITE-C3`) |
|
|
| SameSite (XSRF-TOKEN 쿠키) | `UNSUPPORTED_IMPL_DECISION(a)` — 설정 API 근거 미확보, 착수 시 결정 | D3 · UNSUPPORTED_IMPL_DECISION(a) |
|
|
|
|
<!-- section-id: edge-failure-dependency -->
|
|
## 엣지·실패·의존
|
|
|
|
- **실패·엣지 경로**:
|
|
- **콜백 method 경계 (D3 확정 전 필수 확인)**: Keycloak 콜백이 cross-site top-level **POST**(`response_mode=form_post`)면 `Lax` 가 쿠키를 제외해 **로그인 흐름이 복구 불가능하게 실패**(`RFC6265BIS-SAMESITE-C5`). OAuth2 authorization code flow 기본은 GET 302 redirect(query response mode)라 Lax 호환이나, 실제 설정이 form_post 가 아님을 착수 시 확인.
|
|
- SameSite=Lax 는 top-level navigation GET 을 허용 → GET 기반 state-changing 엔드포인트가 있으면 우회 가능(그래서 CSRF token 병행 — `OWASP-CSRF-SAMESITE-C1`: SameSite 는 proper CSRF 방어 대체 아님). REST 정합(GET=safe method) 설계면 이론적 갭.
|
|
- CSRF token 이 SPA fetch 에 누락 → 정상 요청도 `403`. JS 가 `XSRF-TOKEN` 쿠키를 읽어 `X-XSRF-TOKEN` 헤더로 전송해야 함(`SPRINGSEC-CSRF-C4`).
|
|
- **Spring Session 상호작용**: 세션이 Spring Session(`@EnableSpringHttpSession`, Redis 등)으로 구현되면 `server.servlet.session.cookie.same-site` 가 무시될 수 있다는 vendor 이슈 보고(미검증, official-doc 아님 — `SPRINGBOOT-SESSION-SAMESITE-C1` boundary). 세션 구현체 확인 후 프로퍼티 실효성 재검증.
|
|
- **다른 계약 의존**: `WI-KEYCLOAK-PATTERNS-OVERVIEW-010`(`feature-keycloak-bff-oauth2login-session`)의 cookie-session(`DEC-…-BFF-SESSION-001@1`)을 consume. 세션 쿠키 이름·속성(SameSite 포함)·세션 저장소 구현체(순정 서블릿 vs Spring Session)가 바뀌면 D2·D3 영향 — 특히 D3 의 SameSite 프로퍼티 실효성은 그 구현체에 의존.
|
|
|
|
<!-- section-id: claims-to-verify -->
|
|
## 검증해야 할 주장 / Claims To Verify
|
|
|
|
> 공식 문서는 메커니즘 근거지만 본 프로젝트 실제 동작을 자동 보장하지 않는다. SameSite 소스는 확보됐으나(D3) AP3 코드 미구현이라 구현 후 검증.
|
|
|
|
| Claim | Why uncertain | How to verify | Status |
|
|
|---|---|---|---|
|
|
| BFF cookie-session 에 CSRF 위조 요청이 토큰 없이 성공(재현) | AP3 코드 미구현 · 재현 벡터 미정(b) | cross-site auto-submit form 으로 state-changing 요청 → 성공(방어 전) 확인 | `planned` |
|
|
| CSRF token 적용 후 토큰 없는 위조가 `403` | 코드 미구현 | `.csrf()`+`CookieCsrfTokenRepository` 적용 → 토큰 없는 위조 `403`, 정상(헤더 포함) `200` 확인 | `planned` |
|
|
| SESSION 쿠키 `SameSite=Lax` 가 cross-site 위조 subrequest 를 차단하고 Keycloak 콜백은 유지 | 코드 미구현 · Lax 는 top-level GET 허용이라 콜백 method 에 결과 의존 | `server.servlet.session.cookie.same-site=lax` 적용 → (a) cross-site auto-submit POST 위조가 쿠키 미첨부로 차단 (b) Keycloak 로그인 redirect 정상 완료 확인 | `planned` |
|
|
| Keycloak 콜백이 GET/safe-method top-level navigation (POST/form_post 아님) | AP3 코드·Keycloak client 설정 미구현(`NO_GROUND_TRUTH`) — POST 면 Lax 로 로그인 실패(`RFC6265BIS-SAMESITE-C5`) | Keycloak client `response_mode`·리다이렉트 로그 확인 → GET 302 확인 | `needs-confirmation` |
|
|
| `server.servlet.session.cookie.same-site` 가 실제 세션 쿠키에 적용됨(Spring Session 미간섭) | 세션 구현체 미확정 — Spring Session 사용 시 무시 가능성(vendor 이슈, 미검증) | 응답 `Set-Cookie` 헤더에서 SESSION 쿠키의 `SameSite=Lax` 실측 | `needs-confirmation` |
|
|
|
|
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
|
|
|
|
`/coverage` 실행 전.
|
|
|
|
## 마주친 문제
|
|
|
|
아직 없음.
|
|
|
|
## 묶음 (이 branch에서 파생된 자료)
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[raw/official-docs/csrf-protection-spring-official]]
|
|
- [[raw/official-docs/spring-boot-session-cookie-samesite-property-official]]
|
|
- [[raw/official-docs/samesite-set-cookie-mdn-official]]
|
|
- [[raw/official-docs/rfc6265bis-samesite-attribute-ietf]]
|
|
- [[raw/official-docs/csrf-prevention-cheat-sheet-owasp-samesite-official]]
|
|
- [[raw/official-docs/spring-boot-cookie-samesite-enum-javadoc-official]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
<!-- GENERATED: branches:start -->
|
|
<!-- GENERATED: branches:end -->
|
|
|
|
## 관련 일일 노트
|
|
|
|
해당 없음.
|
|
|
|
## 완료 후 정리
|
|
|
|
- PR 링크:
|
|
- 리뷰 메모:
|