Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/a14-f005-publicpaths-restrictedpathrule.txt
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

139 lines
8.5 KiB
Plaintext

# 등록 순서 — 이 순서가 결과를 정한다
.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();
})
# 규칙 자신은 약해지는 것을 막는다
30: if (requiredAuthorities.isEmpty()) {
31- throw new IllegalArgumentException(
32- "requiredAuthorities must not be empty: a rule that requires nothing is weaker than the "
33- + "authenticated default it replaces");
34- }
# 규칙을 내놓는 자리
main · FileserverAdminPlaneConfiguration.java:36: return new RestrictedPathRule(ADMIN_PATH_PATTERN, settings.security().adminRoles());
# 프로덕션이 등록하는 패턴과 그것을 켜야 하는 두 스위치
@ConditionalOnProperty(
prefix = "app.fileserver-platform.admin",
name = "enabled",
havingValue = "true")
static final String ADMIN_PATH_PATTERN = "/internal/fileserver/**";
-- 그 설정 클래스를 이름으로 부르는 자리 전부
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminManagementContextConfiguration.java:20: * role gate contributed by {@link FileserverAdminPlaneConfiguration} stays: the two guards cover
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminPlaneConfiguration.java:29:public class FileserverAdminPlaneConfiguration {
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java:71: FileserverAdminPlaneConfiguration.class,
-- 그 유일한 @Import 가 든 자동설정과 그 조건
@AutoConfiguration
@ConditionalOnProperty(
prefix = FileserverPlatformSettings.PREFIX,
name = "enabled",
havingValue = "true")
@Import({
FileserverStorageConfiguration.class,
FileserverSecurityConfiguration.class,
FileserverServiceConfiguration.class,
FileserverCleanupConfiguration.class,
FileserverAdminPlaneConfiguration.class,
FileserverStartupConfiguration.class
})
49: public static final String PREFIX = "app.fileserver-platform";
-- 스캔이 그 패키지를 제외하므로 다른 길이 없다
excludeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = AutoConfigurationExcludeFilter.class),
@ComponentScan.Filter(
type = FilterType.REGEX,
pattern = CaSkeletonApplication.AUTO_CONFIGURED_PACKAGES)
})
static final String AUTO_CONFIGURED_PACKAGES =
"dev\\.caskeleton\\.bootstrap\\.autoconfigure\\..*"
+ "|dev\\.caskeleton\\.bootstrap\\.notification\\..*"
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.fileserver\\.admin\\..*"
+ "|dev\\.caskeleton\\.adapter\\.outbound\\.mongo\\..*"
+ "|dev\\.caskeleton\\.adapter\\.outbound\\.messaging\\..*"
+ "|dev\\.caskeleton\\.adapter\\.outbound\\.notification\\..*"
+ "|dev\\.caskeleton\\.adapter\\.outbound\\.persistence\\..*"
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.graphql\\..*"
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.error\\..*"
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.budget\\..*"
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.operation\\..*"
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.webflux\\.error\\..*"
+ "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.webflux\\.operation\\..*"
+ "|dev\\.caskeleton\\.messaging\\..*";
-- 관리 라우트 자체는 관리 컨텍스트에 등록되고 포트는 따로 출하된다
@ManagementContextConfiguration(value = ManagementContextType.ANY, proxyBeanMethods = false)
@ConditionalOnProperty(
prefix = "app.fileserver-platform",
name = {"enabled", "admin.enabled"},
havingValue = "true")
@Import(FileserverAdminController.class)
249: port: ${MANAGEMENT_SERVER_PORT:9001}
317: port: ${APP_SERVER_PORT:8080}
694: public-paths: ${SECURITY_PUBLIC_PATHS:${PRESENTATION_API_BASE_PATH:/v1}/healthcheck}
854: enabled: ${APP_FILESERVER_PLATFORM_ENABLED:false}
899: admin-roles: ${APP_FILESERVER_PLATFORM_SECURITY_ADMIN_ROLES:ROLE_FILE_ADMIN}
911: enabled: ${APP_FILESERVER_PLATFORM_ADMIN_ENABLED:false}
# 공개 경로를 읽는 자리 — 접근자로 읽는 쪽과 원시 키로 읽는 쪽
test · SecuritySettingsTest.java:54: assertThat(context.getBean(SecuritySettings.class).publicPaths())
test · SecuritySettingsTest.java:67: assertThat(settings.publicPaths()).containsExactly("/healthcheck");
test · SecuritySettingsTest.java:68: assertThat(settings.publicPaths()).isUnmodifiable();
main · SecurityConfig.java:75: String[] publicPaths = securitySettings.publicPaths().toArray(new String[0]);
-- 저장소 전체에서 그 키를 쥔 파일. 점 표기와 환경 변수와 중첩 YAML 세 형태로 찾는다
~ .superpowers/sdd/2026-08-02-warning-zero-build-refactoring/task-8-gradle-review.diff
docs/security/public-paths-snapshot.txt
docs/superpowers/plans/2026-07-25-ci-control-plane-recovery.md
docs/superpowers/plans/2026-07-28-notification-production-capability.md
~ src/.env
src/README.md
src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/CorsSecurityFilterIntegrationTest.java
src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/JwtJwksSecurityFilterIntegrationTest.java
src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/SecurityModeWebContractTest.java
src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/settings/SecuritySettingsTest.java
src/app-bootstrap/src/functionalTest/java/dev/caskeleton/bootstrap/contract/BuildVerificationPurityContractTest.java
src/app-bootstrap/src/main/resources/application-local.yml
src/app-bootstrap/src/main/resources/application.yml
src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ProfileSeparationContractTest.java
src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/security/PublicHealthPathAgreementTest.java
src/app-bootstrap/src/test/resources/application-test.yml
src/gradle/public-path-snapshot.gradle
src/sample-portfolio/src/main/resources/application.yml
src/sample-portfolio/src/test/resources/application-test.yml
전부 / 그중 추적되지 않는 것(~) : 19 / 2
그중 제한 패턴을 함께 언급하는 파일 : (0)
# 겹침을 보는 것이 있는가 — 규칙 이름을 쓰는 자리 전부와 공개 경로 스냅숏 게이트
src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/RestrictedPathRule.java
src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/SecurityConfig.java
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminPlaneConfiguration.java
공개 경로 스냅숏 게이트가 있는 파일 : src/gradle/public-path-snapshot.gradle
그 게이트가 읽는 두 입력 :
File publicPathEnvironmentFile = rootProject.file('.env')
rootProject.file('../docs/security/public-paths-snapshot.txt')
값 쪽 입력이 추적되는가 : .gitignore:7:src/.env* src/.env
그 입력이 없으면 :
if (!environmentFile.isFile()) {
throw new GradleException(
"missing public-path environment file ${environmentFile}")
그런데도 그 태스크를 부르는 자리 :
.github/workflows/ci-quality-gates.yml:50: run: ./gradlew check verifyPublicPathSnapshot verifyDependencyLocks --warning-mode=fail --no-daemon --stacktrace
.github/workflows/ci-quality-gates.yml:117: verifyPublicPathSnapshot
.github/workflows/notification-platform.yml:97: ./gradlew verifyEnvKeys verifyPublicPathSnapshot --console=plain
.github/workflows/web-release.yml:48: verifyPublicPathSnapshot
워크플로 전체에서 .env 를 입에 올리는 자리 전부 :
.github/workflows/fileserver-pr.yml:22: - 'src/.env'
.github/workflows/notification-platform.yml:30: - 'src/.env'
그 게이트가 제한 패턴을 언급하는가 : 0