Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/web-inbound-and-http-surface/case/case-analysis-finding-a14-f003.md
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00

7.6 KiB

kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, body, assets, evidence, source
kind slug title topic project status sourceRevision rootTreeNode evidenceCapturedOn body assets evidence source
CASE analysis-finding-a14-f003 플랫폼 요청 컨텍스트가 서블릿에는 생산자가 없고, 리액티브에는 익명 액터로 고정되어 있다 web-inbound-and-http-surface clean-architecture-backend-template 게시 전 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 case:analysis-finding-a14-f003 2026-09-01 case-analysis-finding-a14-f003.body.md
key file
analysis-finding-a14-f003 ../../../final/evidence/rendered/analysis-finding-a14-f003.svg
../../../final/evidence/raw/analysis-finding-a14-f003.txt
원본 분석 절은 analysis/14-adapter-inbound-web.md#L503 이다.

플랫폼 요청 컨텍스트가 서블릿에는 생산자가 없고, 리액티브에는 익명 액터로 고정되어 있다

서블릿 쪽은 컨텍스트를 저장하는 코드가 0 이라 그것을 요구하는 인자 해석기가 항상 던진다. 반응형 쪽은 생산자가 하나 있는데 열 성분 중 넷을 상수로 채운다. 인증 결과가 요청 컨텍스트에 도달하는 경로가 없다.

관계

  • 두 개의 outbox 중 하나만 조립되어 있다 같은 형태의 절반 조립이다.
  • 한쪽의 javadoc이 다른 쪽의 동작을 규탄한다 이 사례가 그 규칙의 형태다.
  • 픽스처가 생산자를 우회하면 게이트가 도는 것은 픽스처다 테스트가 못 잡는 이유다.

문제

플랫폼 요청 컨텍스트는 컨트롤러 서명에서 하위 전송 타입을 몰아내려고 만든 값이다.

두 전송에서 그 값이 어떻게 만들어지는지 확인했다.

결론

두 전송이 각각 다르게 망가져 있다.

서블릿 절반부터 본다.

저장 메서드의 호출자가 저장소 전체에서 0 이다.

그런데 그것을 읽는 쪽은 자동 설정이 등록한다. 인자 해석기를 설정에 추가하는 빈이 있다.

그 해석기가 하는 일은 요구 메서드 하나이고, 속성이 없으면 던진다.

던지는 메시지가 이유를 적는다. 여기서 하나를 지어내면 인증되지 않은 호출이 누군가 고른 익명 행위자처럼 보이게 된다는 것이다.

속성을 놓는 코드가 없으므로 이 예외는 항상 던져진다.

서블릿 컨트롤러가 그 컨텍스트를 매개변수로 선언하면 그 종점은 언제나 서버 오류다.

컨트롤러의 서명에서 하위 전송 타입을 몰아내려고 만든 기능이, 쓰면 반드시 실패하는 기능이다.

반응형 절반은 다르다.

생산자가 하나 있고, 열 성분 중 넷을 상수로 채운다. 주 판본과 행위자와 소속과 지역이다.

이 필터는 보안 문맥 보유자도, 보안 문맥 다리도, 인증 조회도 참조하지 않는다.

인증 결과가 요청 컨텍스트에 도달하는 경로가 없다.

인증된 호출자든 아니든 컨텍스트의 행위자는 익명이고 소속은 없음이다.

같은 저장소가 이 정확한 위험을 서블릿 쪽에서는 이름 붙여 거부한다.

요구 메서드의 메시지가 지어낸 컨텍스트는 인증되지 않은 호출을 누군가 고른 익명 행위자처럼 보이게 한다고 적는다.

반응형 생산자가 하는 일이 정확히 그 지어내기다.

두 전송이 같은 위험에 정반대로 대응했고, 한쪽의 자바독이 다른 쪽의 동작을 규탄한다.

반응형 실패 시나리오는 이렇다.

인증된 사용자가 자기 비동기 작업 결과를 조회한다.

반응형 컨트롤러가 접근 정책을 부르고, 행위자의 인증 여부가 항상 거짓이므로 모든 조회가 거부된다.

정책의 주석이 부재와 금지를 의도적으로 같게 답한다고 적으므로 클라이언트는 없음 응답을 받는다.

비동기 작업 기능이 반응형 전송에서 동작하지 않는다.

서블릿 실패 시나리오는 앞서 본 대로다. 같은 종점이 컨텍스트를 매개변수로 받으면 해석기가 던져 서버 오류이고, 받지 않으면 컨텍스트를 얻을 경로가 없다.

방향은 닫힌 실패다. 데이터 유출이 아니라 기능 불능이다.

그래서 보안 사고가 아니라 지금 틀린 동작으로 P1 이다.

테스트가 잡지 못하는 이유는 구조에 있다.

그 컨텍스트를 만드는 다른 열한 곳이 전부 테스트와 테스트킷이고 전부 손으로 채운다.

계약 레인의 픽스처 컨트롤러조차 해석기를 쓰지 않고 자기 컨텍스트를 만든다.

네 런타임을 가로지르는 교차 스택 게이트가 있어도, 그 게이트가 도는 픽스처가 생산자를 우회한다.

권고는 셋이다.

서블릿에 저장을 부르는 필터를 추가한다. 요청당 한 번 도는 자리가 이미 있다.

두 생산자가 보안 문맥에서 행위자와 소속을 읽게 한다. 그 목적의 다리가 이미 존재한다.

그리고 픽스처가 손으로 컨텍스트를 만드는 대신 해석기와 필터를 지나게 한다. 셋째 없이는 같은 상태가 다시 성립한다.

검증 환경

Spring Boot : 4.0.8 확인 방식 : 호출자 전수 검색과 생산자 코드 확인 소스 수정 : x

재현 조건

원문은 final/evidence/raw/176 계열에 있다.

  1. 서블릿 컨텍스트 보유자의 저장 메서드 호출자를 센다.
  2. 인자 해석기를 등록하는 자동 설정을 확인한다.
  3. 해석기가 부르는 요구 메서드의 실패 경로를 읽는다.
  4. 반응형 생산자가 각 성분을 무엇으로 채우는지 확인한다.
  5. 그 필터가 보안 문맥을 참조하는지 확인한다.
  6. 테스트와 픽스처가 컨텍스트를 어떻게 만드는지 센다.

본문

서블릿 절반. WebMvcRequestContextHolder.store(...)의 호출자가 저장소 전체에서 0이다.

서블릿 쪽 생산자 계수

:::evidence key="analysis-finding-a14-f003" alt="분석 문서 analysis/14-adapter-inbound-web.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/14-adapter-inbound-web.md 발췌 — 15줄" zoom="true" :::

읽는 쪽은 자동설정이 등록한다

// WebMvcPlatformAutoConfiguration.java:158-166
@Bean @ConditionalOnMissingBean(name = "webMvcRequestContextConfigurer")
public WebMvcConfigurer webMvcRequestContextConfigurer() {
  return new WebMvcConfigurer() {
    @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
      resolvers.add(new WebMvcRequestContextArgumentResolver());
    }
  };
}

리졸버가 하는 일은 하나이고 없으면 던진다

WebMvcRequestContextHolder.require(request) 하나이며, 속성이 없으면 던진다 — "no web request context on this request; a fabricated one here would make an unauthenticated call look like an anonymous actor somebody chose". 리액티브 쪽은 익명 액터로 고정되어 있다. P1.

권고

(1) 서블릿에 store를 부르는 필터를 추가한다(WebMvcEvidenceFilter가 이미 요청당 한 번 도는 자리다). (2) 두 생산자가 보안 컨텍스트에서 액터·테넌트를 읽게 한다 — WebSecurityContextBridge가 그 목적으로 이미 존재한다(§12.2). (3) 픽스처가 손으로 컨텍스트를 만드는 대신 리졸버/필터를 지나게 한다. (3) 없이는 같은 상태가 다시 성립한다.

확인하지 못한 것

두 종점에 실제 요청을 보내 오류와 거부를 재현하지 않았다. 호출자 부재와 상수 고정상 그 결과가 나온다.