# ca-skeleton.websocket 어댑터가 등록하는 인터셉터 둘 AuthenticatedHandshakeInterceptor.java 34 줄 WebSocketInboundAuthorizationInterceptor.java 53 줄 stomp/WebSocketConfig.java:32 @ConditionalOnProperty(prefix = "ca-skeleton.websocket", name = "enabled", havingValue = "true") stomp/WebSocketConfig.java:36 private final AuthenticatedHandshakeInterceptor handshakeInterceptor = stomp/WebSocketConfig.java:37 new AuthenticatedHandshakeInterceptor(); stomp/WebSocketConfig.java:40 private final WebSocketInboundAuthorizationInterceptor inboundAuthorization; stomp/WebSocketConfig.java:44 this.inboundAuthorization = new WebSocketInboundAuthorizationInterceptor(properties); stomp/WebSocketConfig.java:53 .addInterceptors(handshakeInterceptor) stomp/WebSocketConfig.java:58 public void configureClientInboundChannel(ChannelRegistration registration) { # 같은 채널에 붙는 세 번째 인터셉터와 그 게이트 advanced/stomp/StompSecurityInterceptor.java 96 줄 advanced/stomp/StompConfiguration.java:24 @ConditionalOnProperty( advanced/stomp/StompConfiguration.java:25 prefix = "app.websocket-platform.advanced.stomp", advanced/stomp/StompConfiguration.java:26 name = "enabled", advanced/stomp/StompConfiguration.java:27 havingValue = "true") advanced/stomp/StompConfiguration.java:33 public StompConfiguration(StompProfile profile, StompSecurityInterceptor securityInterceptor) { advanced/stomp/StompConfiguration.java:35 this.securityInterceptor = securityInterceptor; advanced/stomp/StompConfiguration.java:39 public void configureClientInboundChannel(ChannelRegistration registration) { advanced/stomp/StompConfiguration.java:40 registration.interceptors(securityInterceptor); # 이 모듈이 출하 런타임 그래프에 있는가 app-bootstrap/build.gradle:196 // Explicit qualification-only composition. These projects remain absent from main app-bootstrap/build.gradle:197 // api/implementation/compileOnly/runtimeOnly and therefore from both shipped runtime graphs. app-bootstrap/build.gradle:198 conditionalTransportTestImplementation project(':adapter:inbound:graphql') app-bootstrap/build.gradle:199 conditionalTransportTestImplementation project(':adapter:inbound:grpc') app-bootstrap/build.gradle:200 conditionalTransportTestImplementation project(':adapter:inbound:websocket') websocket/CLAUDE.md:21 The module is absent from both shipped runtime compositions. Being registered and tested does not websocket/CLAUDE.md:22 activate it. A future composition must deliberately add the registered dependency and set websocket/CLAUDE.md:23 `ca-skeleton.websocket.enabled=true` with explicit safe origins. # 정책 계층 — 네 패키지의 파일과 선언 형태와 줄 수 security WebSocketAuthenticationProfile.java enum 57 줄 security WebSocketConnectionTicket.java record 77 줄 security WebSocketOriginPolicy.java final class 116 줄 security WebSocketTicketStore.java interface 32 줄 authz MessageAuthorizationPolicy.java final class 93 줄 idempotency CommandReconciliation.java interface 84 줄 idempotency CommittedResultLedger.java interface 61 줄 idempotency WebSocketCommandKey.java record 40 줄 idempotency WebSocketCommandOutcome.java enum 33 줄 budget WebSocketConnectionBudget.java record 103 줄 합계 10 파일 # 그 열 타입을 stomp 패키지가 참조하는가, 그리고 어디서 생성되는가 CommandReconciliation stomp참조=0 main생성=0 같은파일에@Bean있는main=0 CommittedResultLedger stomp참조=0 main생성=0 같은파일에@Bean있는main=0 MessageAuthorizationPolicy stomp참조=0 main생성=1 같은파일에@Bean있는main=0 WebSocketAuthenticationProfile stomp참조=0 main생성=0 같은파일에@Bean있는main=0 WebSocketCommandKey stomp참조=0 main생성=0 같은파일에@Bean있는main=0 WebSocketCommandOutcome stomp참조=0 main생성=0 같은파일에@Bean있는main=0 WebSocketConnectionBudget stomp참조=0 main생성=1 같은파일에@Bean있는main=0 WebSocketConnectionTicket stomp참조=0 main생성=0 같은파일에@Bean있는main=0 WebSocketOriginPolicy stomp참조=0 main생성=1 같은파일에@Bean있는main=1 WebSocketTicketStore stomp참조=0 main생성=0 같은파일에@Bean있는main=0 # 원문이 22 로 적은 수의 출처 analysis/17:235:# Sub-scope 04 — `security` + `authz` + `idempotency` + `budget` + `error` + `observability` + `admin` + `release` (31 files, main 22 + test 9) security 4 authz 1 idempotency 4 budget 1 error 5 observability 2 admin 2 release 3 여덟 패키지 합계 22 # MessageAuthorizationPolicy 를 받는 자리와 그 받는 쪽의 생성자 authz/MessageAuthorizationPolicy.java:26 public final class MessageAuthorizationPolicy { authz/MessageAuthorizationPolicy.java:39 public static MessageAuthorizationPolicy of(Map> requirements) { authz/MessageAuthorizationPolicy.java:57 public boolean permits(WebSocketMessageType type, Set heldAuthorities) { authz/MessageAuthorizationPolicy.java:70 public boolean declares(WebSocketMessageType type) { authz/MessageAuthorizationPolicy.java:81 public java.util.List undeclaredAmong( authz/MessageAuthorizationPolicy.java:90 public Map> requirements() { WebSocketPlatformStartupValidator.java:3 import dev.caskeleton.adapter.inbound.websocket.authz.MessageAuthorizationPolicy; WebSocketPlatformStartupValidator.java:56 MessageAuthorizationPolicy authorization, config/WebSocketPlatformStartupValidator.java:38 public WebSocketPlatformStartupValidator(boolean productionProfile) { config/WebSocketPlatformStartupValidator.java:53 public void validate( config/WebSocketPlatformStartupValidator.java:106 authorization.undeclaredAmong( WebSocketPlatformStartupValidatorTest.java:40 new WebSocketPlatformStartupValidator(true); WebSocketPlatformStartupValidatorTest.java:42 new WebSocketPlatformStartupValidator(false); # 리프의 CLAUDE.md 가 적는 범위와 인바운드 정책 절 전체 websocket/CLAUDE.md:15 - Opt-in STOMP over SockJS transport configuration and typed validation. websocket/CLAUDE.md:16 - HTTP-handshake principal enforcement and client-inbound STOMP destination authorization. websocket/CLAUDE.md:17 - Fixed client-safe STOMP `ERROR` mapping. websocket/CLAUDE.md:18 - Best-effort in-process event push through an explicit `LiveEventProjector` allowlist. websocket/CLAUDE.md:52 ## Inbound policy websocket/CLAUDE.md:53 websocket/CLAUDE.md:54 - The HTTP upgrade must already have a nonblank `Principal`; the adapter does not authenticate websocket/CLAUDE.md:55 credentials itself. websocket/CLAUDE.md:56 - STOMP `SUBSCRIBE` is allowed only for the configured broadcast destination. websocket/CLAUDE.md:57 - Authenticated `SEND` is allowed only below `/app/**`. websocket/CLAUDE.md:58 - Client `SEND` to `/topic/**` and other destinations is rejected. websocket/CLAUDE.md:59 - Every client-visible processing failure becomes the fixed websocket/CLAUDE.md:60 `WEBSOCKET_REQUEST_REJECTED` ERROR code with an empty payload.