chore: record pre-existing uncommitted repository state
Snapshot of the in-flight state that already existed, identically, in both this worktree and the main checkout before this session began: the initial HTTP Client platform implementation (previously untracked), the redis-lab removal, and the JPA / object-storage / notification integration work. Kept separate from this session's HTTP Client review response, which lands in the following commit, so the two bodies of work stay reviewable apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1a3b560678
commit
5f10b791d3
@@ -39,6 +39,9 @@ production configuration and compare the result with the committed snapshot.
|
||||
단 blank audience 면 검사 건너뜀(기존 settings 계약과 일치).
|
||||
- **JWKS lazy discovery** (`SupplierJwtDecoder`): 기동 시 IdP 가 reachable 일 필요가 없고, 첫 decode
|
||||
시점에 issuer-uri/`.well-known` 네트워크 호출이 일어난다(Spring Boot auto-config 와 동일한 lazy 동작).
|
||||
lazy 초기화 중 외부 discovery/JWKS I/O 실패는 필터 밖 runtime exception으로 탈출시키지 않고
|
||||
`AUTH_JWKS_UNAVAILABLE`로 분류하며, 비-I/O 초기화 실패는 `INTERNAL_AUTH_MISCONFIGURATION`으로
|
||||
fail-closed 한다. 두 carrier 모두 고정 진단만 가지며 원격 응답/URL은 공개 응답에 넣지 않는다.
|
||||
- **Minimal 결정**: JWKS cache TTL 과 unknown-kid rate-limit 은 override 하지 않는다. 정확한 수치는
|
||||
IdP-side token TTL 에 달린 NEEDS_CONTEXT 라 Nimbus/Spring 기본값을 쓰고 문서로만 남긴다.
|
||||
- `jwtValidator` 가 package-private + static 인 이유: 네트워크/IdP 의존 없이 단위 테스트 가능하게 하려고.
|
||||
@@ -65,6 +68,13 @@ arbitrary principal graph와 `SPRING_SECURITY_CONTEXT` 객체는 저장하지
|
||||
손상·초과 snapshot은 인증 없음으로 fail closed한다. 실제 security filter save/restore 테스트가 다음
|
||||
요청에서 principal과 authorities가 복원되고 session에는 primitive snapshot만 남는 것을 검증한다.
|
||||
|
||||
응답 본문 flush/redirect/error가 새 session보다 먼저 commit되지 않도록 repository가 Spring Security의
|
||||
commit-aware response wrapper 계약을 구현한다. 또한 이 모듈은 HTML 로그인 복귀용 request cache를
|
||||
사용하지 않는 API 경계이므로 request cache를 명시적으로 비활성화한다. 따라서 미인증 요청이
|
||||
`DefaultSavedRequest` 같은 framework object를 session에 넣지 않는다. app-bootstrap의
|
||||
`redisSessionHttpIntegrationTest`가 TLS/ACL Redis와 서로 다른 세 개의 web context를 사용해 생성,
|
||||
복구, logout tombstone, stale save 거부, 장애 시 controller 이전 fail-closed를 검증한다.
|
||||
|
||||
### SecurityErrorClassifier
|
||||
- AuthN/AuthZ decision matrix 구현. 실행 앱이 coarse 한 3-way 매핑 대신 registry(`docs/registries/error-codes.yaml`)가
|
||||
선언한 세분화 코드를 방출한다.
|
||||
@@ -158,6 +168,15 @@ arbitrary principal graph와 `SPRING_SECURITY_CONTEXT` 객체는 저장하지
|
||||
- **D5: RFC 7807 `ProblemDetail` 표현은 거부**하고 자체 `Envelope` 형식을 쓴다.
|
||||
- **운영/전송/보안 예외만** 처리한다. 도메인 예외는 소비 모듈의 별도 `@RestControllerAdvice` 가 처리하고
|
||||
Spring 이 두 advice 를 합성(compose)한다(CLAUDE.md 의 `@Order(HIGHEST_PRECEDENCE)` 규칙 참조).
|
||||
- **클라이언트 메시지는 allowlist다.** 예외 메시지, validation interpolated message, rejected
|
||||
request value, raw request URL은
|
||||
`error.message`/`details`에 넣지 않는다. `ClientSafeErrorMessages`의 코드별 고정 문구와
|
||||
정규화된 server-owned field, allowlisted reason code/fixed message, expectedType,
|
||||
supported-method 같은 bounded 구조 메타데이터만 공개한다. collection/map index와 key는 field
|
||||
path에서 제거한다.
|
||||
- 코드별 문구가 명시되지 않은 operational code는 category 기반 고정 문구로 fail-closed 한다. 이
|
||||
fallback은 새 코드를 실수로 진단 문자열에 연결하는 대신 transient/conflict/data-integrity 또는
|
||||
`Internal server error`만 공개한다.
|
||||
- `adapter-web` 에 위치하는 이유: 실행 앱이 어떤 sample 모듈에도 의존하지 않고 envelope 형식 에러 응답을
|
||||
제공하도록.
|
||||
- **`spanErrorRecorder`.** 프로덕션 코드를 특정 트레이서
|
||||
@@ -173,7 +192,7 @@ arbitrary principal graph와 `SPRING_SECURITY_CONTEXT` 객체는 저장하지
|
||||
| `MappingException` | `MAPPING_FAILED` | 400 | B3: 매퍼 내부 실패는 `MAPPING_FAILED` 로, `BAD_PARAMETER`/`INTERNAL_ERROR` 로 보내지 않음 |
|
||||
| `AdapterDisabledException` | `ADAPTER_DISABLED` | 500 (retryable=false) | Layer 3 런타임 fail-fast(integration-adapter-templates §4/D4). 시작-수명주기용 `REQUIRED_ADAPTER_DISABLED` 가 아님(§Audit A2). 예외 메시지의 어댑터 이름은 서버 로그용, 클라이언트는 `client_safe_message` 만 |
|
||||
| `IllegalArgumentException` | `BAD_PARAMETER` | 400 | B3: 매퍼가 아닌 호출자의 일반 예외 |
|
||||
| `ConstraintViolationException` | `VALIDATION_FAILED` | 400 | field/message violation 리스트를 details 로 |
|
||||
| `ConstraintViolationException` | `VALIDATION_FAILED` | 400 | 정규화된 field + allowlisted reason code/fixed message 리스트를 details 로. interpolated message와 iterable key/index는 미노출 |
|
||||
| `MethodArgumentTypeMismatchException` | `BAD_PARAMETER` | 400 | expectedType 을 details 로 |
|
||||
| `InvalidBearerTokenException` | `INVALID_TOKEN` | 코드 상태 | |
|
||||
| `AuthenticationException` | `UNAUTHENTICATED` | 코드 상태 | |
|
||||
@@ -190,9 +209,9 @@ arbitrary principal graph와 `SPRING_SECURITY_CONTEXT` 객체는 저장하지
|
||||
| `HttpMediaTypeNotSupportedException` | `UNSUPPORTED_MEDIA_TYPE` | 415 | D9: 요청 본문 형식 미지원 — 406 과 구별 |
|
||||
| `MaxUploadSizeExceededException` | `PAYLOAD_TOO_LARGE` | 413 | D8: 과대 본문은 envelope 내 413, raw 500 금지. 멀티파트 전용 413(`UPLOAD_SIZE_EXCEEDED`)은 이 영역의 책임 — 병합 후 정제 |
|
||||
| `HttpMediaTypeNotAcceptableException` | `NOT_ACCEPTABLE` | 406 | D9: Accept 에 맞는 표현 없음 — 415 와 구별(합치면 RFC 9110 의미론 상실) |
|
||||
| `MethodArgumentNotValidException` | `VALIDATION_FAILED` | 코드 상태 | field/rejectedValue/message 리스트를 details 로 |
|
||||
| `MethodArgumentNotValidException` | `VALIDATION_FAILED` | 코드 상태 | 정규화된 field + allowlisted reason code/fixed message 리스트를 details 로. rejectedValue/defaultMessage/iterable key/index는 secret/PII 가능성이 있어 미노출 |
|
||||
| `HttpMessageNotReadableException` | `VALIDATION_FAILED` | 코드 상태 | cause 클래스명을 details 로 |
|
||||
| `NoHandlerFoundException` | `ROUTE_NOT_FOUND` | 코드 상태 | |
|
||||
| `NoHandlerFoundException`, `NoResourceFoundException` | `ROUTE_NOT_FOUND` | 코드 상태 | controller/static-resource 어느 404 경로도 같은 Envelope를 사용하고 raw request URL을 echo하지 않음 |
|
||||
| `Exception` (catch-all) | `INTERNAL_ERROR` | 500 | span 에러 기록 + "Internal server error" 고정 메시지 |
|
||||
|
||||
### ErrorResponseFactory
|
||||
|
||||
Reference in New Issue
Block a user