Sub-scope 03 (inbound-web) auth + authz + security
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T07:19:58+00:00

=== OWNED FILES ===
5e06c2caeecc3b985673b29ca845364716bffef9 main/java/dev/caskeleton/adapter/inbound/web/auth/AuthErrorResponseWriter.java 76
d85d9caec98b10eb1e442d3b4f1298c8129b7143 main/java/dev/caskeleton/adapter/inbound/web/auth/AuthenticatedPrincipal.java 19
7968966a4048df417e6283602b1f6931df5736cd main/java/dev/caskeleton/adapter/inbound/web/auth/EnvelopeAccessDeniedHandler.java 35
bb127448ecca12668e76c8016f967d451fb09dce main/java/dev/caskeleton/adapter/inbound/web/auth/EnvelopeAuthenticationEntryPoint.java 35
50fc5f5e1bdc718855d574f4d15149b9841e246d main/java/dev/caskeleton/adapter/inbound/web/auth/JwtDecoderConfig.java 110
2f07433aa5020e83e180f82ac2ed062195d76b66 main/java/dev/caskeleton/adapter/inbound/web/auth/JwtToAuthenticatedPrincipalConverter.java 95
f51dd6464eb972389b507afcc74cfc877533f9b2 main/java/dev/caskeleton/adapter/inbound/web/auth/PrimitiveSessionSecurityContextRepository.java 327
890e5c6a11e71288ac6271c36bf0e20c5d45483a main/java/dev/caskeleton/adapter/inbound/web/auth/RedisSessionWebConfig.java 34
24cf8265230ac6511bb72bcd73b8bd454d380c0e main/java/dev/caskeleton/adapter/inbound/web/auth/RestrictedPathRule.java 41
c5ec53e63559854e6b76349841365e1de4914888 main/java/dev/caskeleton/adapter/inbound/web/auth/SecurityConfig.java 172
dfbe16dc2aaa5e703ea8a77efd92313e7d3d5d60 main/java/dev/caskeleton/adapter/inbound/web/auth/SecurityErrorClassifier.java 132
dcc5991ca3a35b1ed2f260786688aad063942cb1 main/java/dev/caskeleton/adapter/inbound/web/authz/AuthorizationAdapter.java 33
f01b7f01e244081f896e95e4a15efa9e780480ee main/java/dev/caskeleton/adapter/inbound/web/authz/MethodSecurityConfig.java 61
acea282c134c2dd09fedfc1cf2df5d228835a42c main/java/dev/caskeleton/adapter/inbound/web/authz/RequiresPermissionAuthorizationManager.java 87
09be195e7af550b78e4eb81d6b884c7ef89d5c05 main/java/dev/caskeleton/adapter/inbound/web/authz/RolePermissionPolicy.java 36
118fd90282f87299ac77df784417beb6fd60708d main/java/dev/caskeleton/adapter/inbound/web/authz/RolePermissionRegistry.java 52
8f074d09eb566d270475bb60a8ef654cf03b03a8 main/java/dev/caskeleton/adapter/inbound/web/security/AuthenticationView.java 60
5679e864708a5d81a94b689eff2ce13947c7d123 main/java/dev/caskeleton/adapter/inbound/web/security/CorsProfile.java 88
428ba0ac41ccd70cdc97e2f39b80e5239b5867cf main/java/dev/caskeleton/adapter/inbound/web/security/CsrfProfile.java 44
ebacf4fc9590e3ca1913009d8b95891dbc5b9699 main/java/dev/caskeleton/adapter/inbound/web/security/SecurityIdentity.java 22
ceb95d338a365ad220f85d398d46af329389f05a main/java/dev/caskeleton/adapter/inbound/web/security/UntrustedTenantInputException.java 23
8bf20591cd78b894191903952048df0686bd8796 main/java/dev/caskeleton/adapter/inbound/web/security/WebActorContextResolver.java 29
c634486dff5d257f063c91ce58823cef3dc0d755 main/java/dev/caskeleton/adapter/inbound/web/security/WebCorsPolicyValidator.java 96
2efd1eca6c7992b634364ae6bff5b0d0a61d84bf main/java/dev/caskeleton/adapter/inbound/web/security/WebCredentialMode.java 42
348a0596feda88c814690c70b8ed17174209812c main/java/dev/caskeleton/adapter/inbound/web/security/WebCsrfPolicyResolver.java 43
6aaa56cb3cfabcbfaef1976e0a265f26cf89ff6c main/java/dev/caskeleton/adapter/inbound/web/security/WebSecurityContextBridge.java 63
8cf47c6f9457b7032629f739a4a5ab3fb6d81465 main/java/dev/caskeleton/adapter/inbound/web/security/WebTenantContextResolver.java 51
6b721fc0fbea94ac0ce10660fb92ea1070d90523 test/java/dev/caskeleton/adapter/inbound/web/auth/AuthenticatedPrincipalTest.java 33
86bfeab4c99f67b585be3a2e7a340e766b500bd3 test/java/dev/caskeleton/adapter/inbound/web/auth/CorsSecurityFilterIntegrationTest.java 233
9fe3e743b6efc179483bfadb15348e07f91d8148 test/java/dev/caskeleton/adapter/inbound/web/auth/EnvelopeAccessDeniedHandlerTest.java 38
9b8b9427f7b7de3357cbb73713f600bec79fbb6b test/java/dev/caskeleton/adapter/inbound/web/auth/EnvelopeAuthenticationEntryPointTest.java 129
42fc9d36f7673e607f3310c0d6ed08dee976dce1 test/java/dev/caskeleton/adapter/inbound/web/auth/JwtDecoderConfigTest.java 93
fc807a30af5fbefb14810df62e78e1931aefed76 test/java/dev/caskeleton/adapter/inbound/web/auth/JwtJwksSecurityFilterIntegrationTest.java 455
75de183b8b3fbbfbdaf757521a0dba368c67a4ff test/java/dev/caskeleton/adapter/inbound/web/auth/JwtToAuthenticatedPrincipalConverterTest.java 34
3abf02fb8d3481703cf7aa7cb7e18c4b1a3bbe96 test/java/dev/caskeleton/adapter/inbound/web/auth/PrimitiveSessionSecurityContextRepositoryTest.java 226
b35c4e6caa0689fa088c575db34c6917cf05f8b2 test/java/dev/caskeleton/adapter/inbound/web/auth/RedisSessionWebConfigTest.java 72
9be1c45d31d63ad45a2f559ccd00a4690f45e655 test/java/dev/caskeleton/adapter/inbound/web/auth/SecurityErrorClassifierTest.java 166
b736fd3246ab5a0be66feb6fc0e862a2080ed8fe test/java/dev/caskeleton/adapter/inbound/web/auth/SecurityModeWebContractTest.java 220
513127906df4e0421575a95e36f10c0b6a1bcc5a test/java/dev/caskeleton/adapter/inbound/web/authz/AuthorizationAdapterTest.java 47
3e8f3d2672bfc5778c7811a98e4896a8d83a78a7 test/java/dev/caskeleton/adapter/inbound/web/authz/RequiresPermissionAuthorizationManagerTest.java 141
94e24a09f847acbe8018ca304d91ba31ba891d50 test/java/dev/caskeleton/adapter/inbound/web/authz/RolePermissionPolicyTest.java 69
e20d02e10f8d2395d0a3ac57a9c47e1005b23570 test/java/dev/caskeleton/adapter/inbound/web/authz/RolePermissionRegistryTest.java 70
e8d4107c1e66140e2d16e773278f033a26c2a4c6 test/java/dev/caskeleton/adapter/inbound/web/security/WebCorsCsrfPolicyTest.java 176
80279cedb7a4d2b0cc4cdee0aaac7607a4d3c684 test/java/dev/caskeleton/adapter/inbound/web/security/WebSecurityContextBridgeTest.java 106
file_count=44

=== 8.1 도달성: 신원 모델이 프로덕션에서 참조되는가 ===
WebSecurityContextBridge : main_refs=0 test_refs=1
WebActorContextResolver : main_refs=1 test_refs=0
WebTenantContextResolver : main_refs=1 test_refs=1
AuthenticationView : main_refs=3 test_refs=1
SecurityIdentity : main_refs=1 test_refs=1
rejectTenantInput : main_refs=2 test_refs=1
WebCorsPolicyValidator : main_refs=0 test_refs=1
WebCsrfPolicyResolver : main_refs=0 test_refs=1

$ grep -rn 'new AuthenticationView(\|AuthenticationView\.\(authenticated\|anonymous\)' --include=*.java adapter/inbound/web/src | grep -v 'security/AuthenticationView.java' | sed 's|adapter/inbound/web/src/||'
test/java/dev/caskeleton/adapter/inbound/web/security/WebSecurityContextBridgeTest.java:27:        AuthenticationView.authenticated("actor-1", Set.of("ROLE_USER"), "tenant-authenticated");
test/java/dev/caskeleton/adapter/inbound/web/security/WebSecurityContextBridgeTest.java:42:                bridge.resolve(AuthenticationView.anonymous(), Map.of("tenant", "tenant-attacker")))
test/java/dev/caskeleton/adapter/inbound/web/security/WebSecurityContextBridgeTest.java:64:    SecurityIdentity identity = bridge.resolve(AuthenticationView.anonymous());
test/java/dev/caskeleton/adapter/inbound/web/security/WebSecurityContextBridgeTest.java:75:        bridge.resolve(AuthenticationView.authenticated("actor-1", Set.of("ROLE_ADMIN")));
test/java/dev/caskeleton/adapter/inbound/web/security/WebSecurityContextBridgeTest.java:90:                new AuthenticationView(
exit=0

=== 8.2 두 전송의 WebRequestContext 생산자 ===
$ grep -rn 'new WebRequestContext(' --include=*.java adapter/inbound/web/src app-bootstrap/src | sed 's|adapter/inbound/web/src/||'
test/java/dev/caskeleton/adapter/inbound/web/core/WebRequestContextTest.java:100:    return new WebRequestContext(
test/java/dev/caskeleton/adapter/inbound/web/mvc/idempotency/WebMvcIdempotentInvokerTest.java:237:    return new WebRequestContext(
test/java/dev/caskeleton/adapter/inbound/web/webflux/idempotency/WebFluxIdempotentInvokerTest.java:212:    return new WebRequestContext(
test/java/dev/caskeleton/adapter/inbound/web/webflux/context/WebFluxRequestContextAccessorTest.java:81:    return new WebRequestContext(
test/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationAccessPolicyTest.java:54:        new WebRequestContext(
test/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationAccessPolicyTest.java:110:    return new WebRequestContext(
main/java/dev/caskeleton/adapter/inbound/web/webflux/context/WebFluxRequestContextFilter.java:81:        new WebRequestContext(
testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureController.java:143:    return new WebRequestContext(
testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ReactiveFaultFixtureController.java:141:    return new WebRequestContext(
testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureSupport.java:63:    return new WebRequestContext(
testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureSupport.java:123:    return new WebRequestContext(
exit=0

$ sed -n '77,90p' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/webflux/context/WebFluxRequestContextFilter.java
    ServerHttpRequest request = exchange.getRequest();
    Instant receivedAt = clock.instant();
    WebRequestId requestId = resolveRequestId(request);
    WebRequestContext context =
        new WebRequestContext(
            requestId,
            resolveTraceId(request),
            operationName(request),
            new ApiMajorVersion(1),
            ActorContext.anonymous(),
            TenantContext.none(),
            Locale.ENGLISH,
            receivedAt,
            receivedAt.plus(requestBudget),
exit=0

$ grep -rn 'WebMvcRequestContextHolder.store' --include=*.java . | wc -l
0
exit=0

$ grep -rn 'WebMvcRequestContextHolder' --include=*.java adapter/inbound/web/src app-bootstrap/src | grep -v 'WebMvcRequestContextHolder.java' | sed 's|adapter/inbound/web/src/||'
main/java/dev/caskeleton/adapter/inbound/web/mvc/context/WebMvcRequestContextArgumentResolver.java:40:    return WebMvcRequestContextHolder.require(request);
exit=0

$ grep -n 'require(request)\|orElseThrow' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/mvc/context/WebMvcRequestContextHolder.java adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/mvc/context/WebMvcRequestContextArgumentResolver.java
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/mvc/context/WebMvcRequestContextHolder.java:60:        .orElseThrow(
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/mvc/context/WebMvcRequestContextArgumentResolver.java:40:    return WebMvcRequestContextHolder.require(request);
exit=0

$ grep -n 'addArgumentResolvers' -A 4 adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/mvc/autoconfigure/WebMvcPlatformAutoConfiguration.java
163:      public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
164-        resolvers.add(new WebMvcRequestContextArgumentResolver());
165-      }
166-    };
167-  }
exit=0

=== 8.3 필터 체인 순서: publicPaths 대 RestrictedPathRule ===
$ sed -n '83,94p' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/SecurityConfig.java
        .authorizeHttpRequests(
            auth -> {
              if (publicPaths.length > 0) {
                auth.requestMatchers(publicPaths).permitAll();
              }
              // Ordered before the authenticated catch-all: a management path must be refused at
              // the transport, not by an application policy the request has already passed.
              for (RestrictedPathRule rule : restricted) {
                auth.requestMatchers(rule.pathPattern()).hasAnyAuthority(rule.authorities());
              }
              auth.anyRequest().authenticated();
            })
exit=0

$ grep -rn 'new RestrictedPathRule(' --include=*.java app-bootstrap/src/main adapter
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminPlaneConfiguration.java:36:    return new RestrictedPathRule(ADMIN_PATH_PATTERN, settings.security().adminRoles());
exit=0

$ grep -rn 'public-paths' app-bootstrap/src/main/resources/application.yml
694:    public-paths: ${SECURITY_PUBLIC_PATHS:${PRESENTATION_API_BASE_PATH:/v1}/healthcheck}
exit=0

=== 8.4 mayAccess 가 익명 액터에 대해 무엇을 하는가 ===
$ sed -n '28,41p' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationAccessPolicy.java
  public static boolean mayAccess(DurableOperation operation, WebRequestContext context) {
    Objects.requireNonNull(operation, "operation");
    Objects.requireNonNull(context, "context");
    if (!context.actor().authenticated()) {
      return false;
    }
    if (!operation.principal().equals(context.actor().subject())) {
      return false;
    }
    String tenant = operation.tenantId();
    return tenant == null
        ? context.tenant().value().isEmpty()
        : context.tenant().value().filter(tenant::equals).isPresent();
  }
exit=0

