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>
165 lines
7.6 KiB
Markdown
165 lines
7.6 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: analysis-finding-a14-f003
|
|
title: 플랫폼 요청 컨텍스트가 서블릿에는 생산자가 없고, 리액티브에는 익명 액터로 고정되어 있다
|
|
topic: web-inbound-and-http-surface
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: case:analysis-finding-a14-f003
|
|
evidenceCapturedOn: 2026-09-01
|
|
body: case-analysis-finding-a14-f003.body.md
|
|
assets:
|
|
- key: analysis-finding-a14-f003
|
|
file: ../../../final/evidence/rendered/analysis-finding-a14-f003.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/analysis-finding-a14-f003.txt
|
|
source:
|
|
- 원본 분석 절은 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. 테스트와 픽스처가 컨텍스트를 어떻게 만드는지 센다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
**서블릿 절반.** `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"
|
|
:::
|
|
|
|
## 읽는 쪽은 자동설정이 등록한다
|
|
|
|
```java
|
|
// 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) 없이는 같은 상태가 다시 성립한다.
|
|
|
|
## 확인하지 못한 것
|
|
|
|
두 종점에 실제 요청을 보내 오류와 거부를 재현하지 않았다. 호출자 부재와 상수 고정상 그 결과가 나온다.
|
|
|
|
<!-- body:end -->
|