diff --git a/backend-spring/src/main/java/com/erp/security/SecurityConfig.java b/backend-spring/src/main/java/com/erp/security/SecurityConfig.java index ebad3e01..7a88baee 100644 --- a/backend-spring/src/main/java/com/erp/security/SecurityConfig.java +++ b/backend-spring/src/main/java/com/erp/security/SecurityConfig.java @@ -59,21 +59,26 @@ public class SecurityConfig { .requestMatchers("/api/**").permitAll() .anyRequest().authenticated() ) - .addFilterBefore(new AiApiKeyAuthFilter(aiAgentApiKeyService), - JwtAuthenticationFilter.class) + // ⚠️ Spring Security 6 부터 addFilterBefore/After 의 anchor 는 _이미 등록된_ + // 필터여야 함. 따라서 JwtAuthenticationFilter 를 가장 먼저 (Spring 표준 + // UsernamePasswordAuthenticationFilter 기준으로) 등록한 뒤, 나머지 커스텀 + // 필터들이 JwtAuthenticationFilter 를 anchor 로 사용한다. .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) + // Phase 2 (2026-04-24): 서브도메인 → CompanyResolver → TenantRoutingDataSource 라우팅. + // JwtAuthenticationFilter 보다 앞에서 실행되어야 tenant 컨텍스트가 먼저 결정됨. + .addFilterBefore( + new SubdomainResolverFilter(companyResolver, tenantRoutingDataSource, tenantDbSettings), + JwtAuthenticationFilter.class) + // AiApi 키 인증 — jwt 앞에서 sk-pipe-* 형식 처리, 매칭되지 않으면 jwt 로 통과. + .addFilterBefore(new AiApiKeyAuthFilter(aiAgentApiKeyService), + JwtAuthenticationFilter.class) // JwtAuthenticationFilter 뒤 — JWT.company_code 와 서브도메인의 company_code 대조. .addFilterAfter(new TenantConsistencyGuardFilter(jwtTokenProvider), JwtAuthenticationFilter.class) // TenantConsistencyGuardFilter 뒤 — 비번 강제 변경 대기자는 허용 경로만 통과. .addFilterAfter(new ForcePasswordChangeGuardFilter(jwtTokenProvider), - TenantConsistencyGuardFilter.class) - // Phase 2 (2026-04-24): 서브도메인 → CompanyResolver → TenantRoutingDataSource 라우팅. - // JwtAuthenticationFilter 보다 앞에서 실행되어야 tenant 컨텍스트가 먼저 결정됨. - .addFilterBefore( - new SubdomainResolverFilter(companyResolver, tenantRoutingDataSource, tenantDbSettings), - JwtAuthenticationFilter.class); + TenantConsistencyGuardFilter.class); return http.build(); }