Identity DSL
Identity DSL은 전체 인증 플랫폼을 설정하기 위한 플루언트, 타입 세이프 Java API입니다. 인증 방법, 상태 관리, Spring Security 설정을 단일 PlatformSecurityConfig 클래스에서 정의합니다.
빠른 시작
가장 간단한 설정 -- 세션 기반 상태의 폼 로그인:
@Configuration
@EnableWebSecurity
public class PlatformSecurityConfig {
@Bean
public PlatformConfig platformDslConfig(
IdentityDslRegistry<HttpSecurity> registry) throws Exception {
return registry
.form(form -> form.loginPage("/login").defaultSuccessUrl("/home"))
.session(Customizer.withDefaults())
.build();
}
}
왜 Contexa DSL인가?
Spring Security는 모든 SecurityFilterChain에 대해 별도의 @Bean 메서드가 필요합니다. 3개의 로그인 페이지는 반복되는 보일러플레이트를 가진 3개의 메서드를 의미합니다. Contexa의 Identity DSL은 이를 크게 줄여 하나의 플루언트 체인과 하나의 메서드에서 여러 Filter Chain 구성을 조립할 수 있게 합니다.
Spring Security: 3개의 Filter Chain
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean @Order(1)
public SecurityFilterChain adminChain(HttpSecurity http) throws Exception {
return http
.securityMatcher("/admin/**")
.csrf(AbstractHttpConfigurer::disable)
.formLogin(form -> form.loginPage("/admin/login").defaultSuccessUrl("/admin"))
.sessionManagement(session -> session.sessionFixation().changeSessionId())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.build();
}
@Bean @Order(2)
public SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
return http
.securityMatcher("/api/**")
.csrf(AbstractHttpConfigurer::disable)
.formLogin(form -> form.loginPage("/api/login").defaultSuccessUrl("/api"))
.sessionManagement(session -> session.sessionFixation().changeSessionId())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.build();
}
@Bean @Order(3)
public SecurityFilterChain userChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.formLogin(form -> form.loginPage("/login").defaultSuccessUrl("/home"))
.sessionManagement(session -> session.sessionFixation().changeSessionId())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.build();
}
}
// 3개의 @Bean 메서드, ~40줄, 체인마다 반복되는 보일러플레이트
Contexa Identity DSL: 동일한 결과
@Bean
public PlatformConfig platformDslConfig(
IdentityDslRegistry<HttpSecurity> registry) throws Exception {
return registry
.global(http -> {
http.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionFixation().changeSessionId())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
})
.form(f -> f.order(10).loginPage("/admin/login").defaultSuccessUrl("/admin")
.rawHttp(http -> http.securityMatcher("/admin/**")))
.session(Customizer.withDefaults())
.form(f -> f.order(20).loginPage("/api/login").defaultSuccessUrl("/api")
.rawHttp(http -> http.securityMatcher("/api/**")))
.session(Customizer.withDefaults())
.form(f -> f.order(30).loginPage("/login").defaultSuccessUrl("/home"))
.session(Customizer.withDefaults())
.build();
}
// 1개 메서드, ~15줄, 보일러플레이트를 크게 줄이고 런타임에 3개의 SecurityFilterChain 빈 등록
.form().session().form().session().build() -- 하나의 플루언트 체인이 자동으로 여러 Filter Chain을 생성SecurityFilterChainRegistrar가 BeanDefinitionRegistry를 통해 런타임에 SecurityFilterChain 빈을 등록.session() 또는 .oauth2())와 결합 가능. Spring Security는 세션 전용으로 제한됨.DSL 아키텍처
DSL은 2단계 체인 빌더 패턴을 따릅니다. 각 .auth().state() 쌍이 독립적인 SecurityFilterChain을 생성하며, 우선순위에 따라 정렬됩니다.
2단계 빌더 패턴
하나의 Registry에서 다중 Chain
각 .auth().state() 쌍이 별도의 SecurityFilterChain을 생성합니다. Registry가 모든 쌍을 수집하고 SecurityFilterChainRegistrar가 런타임에 Spring 빈으로 등록합니다.
각 체인은 자체 필터, 인증 전략 및 상태 관리로 격리됩니다. 요청은 order()와 securityMatcher에 의해 라우팅됩니다 -- 첫 번째 매칭 체인이 요청을 처리합니다.
동작 원리: 동적 빈 등록
PlatformConfig를 반환하는 하나의 @Bean 메서드를 작성합니다. Contexa의 부트스트랩 파이프라인이 이를 읽고 자동으로 여러 SecurityFilterChain 빈을 등록합니다 -- 추가 @Bean 어노테이션이 필요 없습니다.
작성하는 것 vs. Spring이 보는 것
@Bean 메서드PlatformConfig 반환SecurityFilterChain 빈런타임에 등록
formSecurityFilterChain1restSecurityFilterChain2ottSecurityFilterChain3부트스트랩 파이프라인
order=10, matcher=/admin/**
order=20, matcher=/api/**
order=30, matcher=/**
독립 필터
독립 필터
독립 필터
로그인 페이지, 필터
bearer 필터
매직 링크 필터
order=10
order=20
order=30
SecurityFilterChainRegistrar는 Spring의 BeanDefinitionRegistry에 직접 접근합니다. PlatformConfig를 반환하는 하나의 @Bean 메서드를 작성하면, Spring은 각각에 대해 별도의 @Bean 메서드를 작성한 것과 정확히 동일하게 여러 SecurityFilterChain 빈을 인식합니다.
글로벌 설정
global()은 공유 HTTP 보안 설정을 모든 Filter Chain에 적용합니다. 개별 인증 방법 설정은 충돌 시 글로벌 설정을 오버라이드합니다 (SecurityConfigurerOrchestrator가 글로벌을 먼저 적용한 후 플로우별 설정을 적용).
registry
.global(http -> {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authReq -> authReq
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
.anyRequest().access(customAuthorizationManager))
.securityContext(sc ->
sc.securityContextRepository(customRepository));
})
// ... 인증 방법이 이어짐
오버라이드 규칙: 글로벌 설정기가 먼저 실행되고, 각 플로우의 자체 설정기가 실행됩니다. 플로우가 disableCsrf() 또는 rawHttp()를 설정하면, 해당 특정 체인에 대해서만 글로벌 CSRF 설정이 오버라이드됩니다.
Filter Chain 순서 & 요청 라우팅
각 인증 방법에는 기본 order() 값이 있습니다. 낮은 order = 높은 우선순위 = 먼저 요청을 가로챕니다. 여러 Filter Chain이 등록되면 Spring Security는 순서대로 평가하고 securityMatcher가 요청과 매칭되는 첫 번째 체인을 사용합니다.
| 인증 방법 | 기본 Order | 설명 |
|---|---|---|
| Form Login | 100 | 기존 폼 기반 로그인 |
| MFA | 200 | 다중 인증 |
| REST API | 200 | JSON 자격 증명 제출 |
| One-Time Token | 300 | 매직 링크 / OTT |
| Passkey | 400 | WebAuthn / FIDO2 |
order()와 rawHttp(http -> http.securityMatcher(...))를 사용하여 어떤 체인이 어떤 요청을 처리할지 제어합니다:
registry
.global(http -> { /* 공유 설정 */ })
// 관리자 페이지: Form 로그인, order 20 (최고 우선순위)
.form(f -> f.order(20).loginPage("/admin/login")
.rawHttp(http -> http.securityMatcher("/admin/**")))
.session(Customizer.withDefaults())
// API 엔드포인트: REST 로그인, order 50
.rest(r -> r.order(50).loginProcessingUrl("/api/auth")
.rawHttp(http -> http.securityMatcher("/api/**")))
.oauth2(Customizer.withDefaults())
// 일반 사용자: MFA, order 100
.mfa(m -> m.order(100)
.primaryAuthentication(p -> p.formLogin(form -> form.loginPage("/login")))
.passkey(Customizer.withDefaults()))
.session(Customizer.withDefaults())
.build();
securityMatcher 없이는 체인이 모든 요청에 매칭됩니다. 여러 체인을 사용할 때는 겹침을 방지하기 위해 항상 rawHttp()를 통해 securityMatcher를 설정하세요.
인증 + 상태 조합
모든 인증 방법은 세션 또는 OAuth2 상태와 결합할 수 있습니다. 서로 다른 체인에 걸쳐 다른 상태 전략을 혼합할 수 있습니다.
| 인증 방법 | + Session | + OAuth2 |
|---|---|---|
| Form Login | .form(...).session(...) | .form(...).oauth2(...) |
| REST API | .rest(...).session(...) | .rest(...).oauth2(...) |
| One-Time Token | .ott(...).session(...) | .ott(...).oauth2(...) |
| Passkey | .passkey(...).session(...) | .passkey(...).oauth2(...) |
| MFA | .mfa(...).session(...) | .mfa(...).oauth2(...) |
플로우별 상태: .state() 호출은 가장 최근에 등록된 인증 방법에 적용됩니다. 이를 통해 체인별로 다른 상태 전략을 사용할 수 있습니다:
registry
.form(f -> f.loginPage("/login"))
.session(Customizer.withDefaults()) // form -> Session
.rest(r -> r.loginProcessingUrl("/api/auth"))
.oauth2(Customizer.withDefaults()) // rest -> OAuth2
.mfa(m -> m.primaryAuthentication(...).passkey(...))
.session(Customizer.withDefaults()) // mfa -> Session
.build();
동일한 인증 방법을 여러 번 등록할 수 있습니다 (자동 명명: form_flow, form_flow_2 등).
Spring Security: 세션 전용
Spring Security의 formLogin()은 세션 기반 상태 관리에 고정되어 있습니다. 토큰 기반 인증을 위해서는 oauth2ResourceServer()로 완전히 별도의 SecurityFilterChain을 수동으로 설정해야 합니다.
// Spring Security: 세션 상태가 암묵적, 체인별 전환 불가
http.formLogin(form -> form.loginPage("/login"));
// 항상 세션 기반. 변경 불가.
// 토큰 기반 인증을 위해서는 완전히 별도의 체인이 필요:
@Bean
public SecurityFilterChain resourceServerChain(HttpSecurity http) throws Exception {
return http
.securityMatcher("/api/**")
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
}
// 두 가지 다른 패러다임, 두 가지 다른 설정, 통합 없음
Contexa DSL: 모든 인증 x 모든 상태
Contexa는 인증과 상태 관리를 분리합니다. 동일한 .form()이 한 체인에서는 .session()을, 다른 체인에서는 .oauth2()를 사용할 수 있습니다 -- 단일 메서드에서.
registry
// 관리자: OAuth2 토큰 상태의 폼 로그인
.form(f -> f.order(20).loginPage("/admin/login")
.rawHttp(http -> http.securityMatcher("/admin/**")))
.oauth2(Customizer.withDefaults())
// API: OAuth2 토큰 상태의 REST 로그인 (Stateless)
.rest(r -> r.order(50).loginProcessingUrl("/api/auth")
.rawHttp(http -> http.securityMatcher("/api/**")))
.oauth2(Customizer.withDefaults())
// 사용자: 세션 상태의 MFA
.mfa(m -> m.order(100)
.primaryAuthentication(auth -> auth.formLogin(form -> form.loginPage("/login")))
.passkey(Customizer.withDefaults()))
.session(Customizer.withDefaults())
.build();
// 5가지 인증 방법 x 2가지 상태 유형 = 10가지 조합, 모두 하나의 메서드에서
rawHttp() -- 전체 Spring Security 접근
rawHttp()는 전체 Spring Security HttpSecurity API를 노출하는 SafeHttpCustomizer<HttpSecurity> 훅을 제공합니다. 체크 예외를 지원하며 여러 번 호출할 수 있습니다 (각 호출이 누적됨).
.form(f -> f.order(20)
.rawHttp(http -> http
.securityMatcher("/admin/**")
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class))
.rawHttp(http -> http
.headers(headers -> headers.frameOptions(fo -> fo.deny())))
)
모든 인증 방법과 global()에서 사용 가능합니다. 폼 로그인의 경우 HttpSecurity.formLogin() 내부를 직접 커스터마이즈하는 rawFormLogin()도 있습니다.
SecurityFilterChain 커스터마이제이션
globalHttpCustomizer 람다는 HttpSecurity 빌더를 수신하여 Spring Security의 설정 API에 대한 완전한 접근을 제공합니다. 아래는 일반적인 커스터마이제이션 포인트입니다.
CSRF 설정
기본 설정은 CSRF를 비활성화합니다. HTML 폼을 서빙하는 애플리케이션의 경우 쿠키 기반 토큰 리포지토리와 함께 CSRF를 활성화하는 것을 고려하세요:
// CSRF 비활성화 (기본값 - Stateless API에 적합)
http.csrf(AbstractHttpConfigurer::disable);
// 쿠키 리포지토리로 CSRF 활성화 (HTML 폼 애플리케이션용)
http.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
AISessionSecurityContextRepository를 사용한 세션 관리
AISessionSecurityContextRepository는 Spring의 HttpSessionSecurityContextRepository를 AI 강화 세션 추적으로 확장합니다. 행동 데이터(세션 패턴, 접근 빈도, 이상 점수)를 인가 파이프라인에 제공합니다.
// AI 세션 컨텍스트를 보안 Filter Chain에 연결
http.securityContext(sc ->
sc.securityContextRepository(aiSessionSecurityContextRepository)
);
// Stateless REST API의 경우, form/rest 체인 설정에서
// Stateless 세션 관리를 사용:
registry.form(form -> form
.order(20)
.loginPage("/admin/login")
.defaultSuccessUrl("/admin")
.rawHttp(http -> http.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)))
);
CORS 설정
SafeHttpCustomizer<HttpSecurity> globalHttpCustomizer = http -> {
http
.cors(cors -> cors.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowCredentials(true);
return config;
}))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authReq -> authReq
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
.anyRequest().access(customDynamicAuthorizationManager)
)
.securityContext(sc ->
sc.securityContextRepository(aiSessionSecurityContextRepository));
};
커스텀 필터
개별 체인 설정의 rawHttp 메서드를 사용하여 커스텀 필터를 추가하거나, 크로스 체인 적용을 위해 글로벌 커스터마이저에 포함합니다:
// 인가 필터 앞에 커스텀 필터 추가
SafeHttpCustomizer<HttpSecurity> globalHttpCustomizer = http -> {
http
.addFilterBefore(new TenantContextFilter(),
AuthorizationFilter.class)
.authorizeHttpRequests(authReq -> authReq
.anyRequest().access(customDynamicAuthorizationManager)
);
};
공통 옵션
모든 인증 방법이 공유하는 옵션입니다 (AbstractOptions에서 제공):
| 옵션 | 설명 |
|---|---|
order(int) | Filter Chain 우선순위 (낮을수록 우선순위 높음) |
loginProcessingUrl(String) | 로그인 요청을 처리하는 URL |
defaultSuccessUrl(String) | 로그인 성공 후 리다이렉트 |
failureUrl(String) | 로그인 실패 후 리다이렉트 |
successHandler(PlatformAuthenticationSuccessHandler) | 커스텀 성공 핸들러 |
failureHandler(PlatformAuthenticationFailureHandler) | 커스텀 실패 핸들러 |
securityContextRepository(SecurityContextRepository) | 보안 컨텍스트 저장소 오버라이드 |
disableCsrf() | 이 체인의 CSRF 비활성화 |
cors(Customizer) | CORS 설정 |
headers(Customizer) | HTTP 헤더 설정 |
sessionManagement(Customizer) | 세션 관리 설정 |
logout(Customizer) | 로그아웃 설정 |
rawHttp(SafeHttpCustomizer) | 전체 Spring Security API 접근 (누적됨) |
authorizeStaticPermitAll(String...) | 인증 없이 정적 리소스 허용 |
asep(Customizer) | 이 플로우의 ASEP 어노테이션 속성 |
완전한 예제
관리자 폼 로그인(OAuth2), 사용자 MFA(Session), API REST 엔드포인트(OAuth2)를 포함한 프로덕션 레디 설정:
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class PlatformSecurityConfig {
private final CustomDynamicAuthorizationManager authorizationManager;
private final AISessionSecurityContextRepository securityContextRepository;
@Bean
public PlatformConfig platformDslConfig(
IdentityDslRegistry<HttpSecurity> registry) throws Exception {
return registry
// 글로벌: 모든 체인에 공유
.global(http -> {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authReq -> authReq
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
.anyRequest().access(authorizationManager))
.securityContext(sc ->
sc.securityContextRepository(securityContextRepository));
})
// 관리자: OAuth2를 사용하는 폼 로그인, 최고 우선순위
.form(f -> f.order(20).loginPage("/admin/login")
.defaultSuccessUrl("/admin/dashboard")
.rawHttp(http -> http.securityMatcher("/admin/**")))
.oauth2(Customizer.withDefaults())
// API: OAuth2를 사용하는 REST 로그인
.rest(r -> r.order(50).loginProcessingUrl("/api/auth")
.rawHttp(http -> http
.securityMatcher("/api/**")
.sessionManagement(s ->
s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))))
.oauth2(Customizer.withDefaults())
// 사용자: 세션을 사용하는 MFA
.mfa(m -> m.order(100)
.primaryAuthentication(auth ->
auth.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home")))
.passkey(Customizer.withDefaults()))
.session(Customizer.withDefaults())
.build();
}
}
마이그레이션 가이드
기존 Spring Security 설정에서 Contexa의 PlatformSecurityConfig로 마이그레이션하려면 SecurityFilterChain 빈을 DSL 레지스트리 패턴으로 교체하고 동적 인가 매니저를 연결해야 합니다.
변경 전: 기존 Spring Security
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(
HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").hasRole("USER")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/admin")
)
.build();
}
}
변경 후: Contexa PlatformSecurityConfig
@EnableWebSecurity
@RequiredArgsConstructor
public class PlatformSecurityConfig {
private final CustomDynamicAuthorizationManager customDynamicAuthorizationManager;
private final AISessionSecurityContextRepository aiSessionSecurityContextRepository;
@Bean
public PlatformConfig platformDslConfig(
IdentityDslRegistry<HttpSecurity> registry) throws Exception {
SafeHttpCustomizer<HttpSecurity> globalHttpCustomizer = http -> {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**", "/js/**", "/images/**", "/favicon.ico")
.permitAll()
.anyRequest()
.access(customDynamicAuthorizationManager)
)
.securityContext(sc -> sc
.securityContextRepository(aiSessionSecurityContextRepository));
};
return registry
.global(globalHttpCustomizer)
.form(form -> form.order(20)
.loginPage("/admin/login")
.defaultSuccessUrl("/admin"))
.oauth2(Customizer.withDefaults())
.build();
}
}
마이그레이션 단계
spring-boot-starter-contexa를 추가합니다. 이는 contexa-iam, contexa-core, contexa-identity 모듈을 전이적으로 포함합니다.security.zerotrust.mode=SHADOW를 설정하여 동적 인가를 감사 전용 모드로 실행합니다. 결정은 로그에 기록되지만 적용되지 않아 정책이 예상 동작과 일치하는지 확인할 수 있습니다.SecurityFilterChain 빈을 PlatformConfig 패턴으로 교체합니다. 정적 permitAll() 규칙을 requestMatchers로 이동하고 모든 hasRole() / hasAuthority()를 .anyRequest().access(customDynamicAuthorizationManager)로 교체합니다.security.zerotrust.mode=ENFORCE를 설정하여 실시간 정책 적용을 활성화합니다. 전환 기간 동안 인가 감사 로그를 면밀히 모니터링합니다.마이그레이션 체크리스트
| 단계 | 액션 | 검증 |
|---|---|---|
| 1 | spring-boot-starter-contexa 의존성 추가 | 오류 없이 애플리케이션 시작 |
| 2 | 리소스 스캐너를 실행하여 엔드포인트 검색 | Policy Center의 리소스 화면에서 스캔된 리소스 확인 가능 |
| 3 | 각 엔드포인트에 대한 XACML 정책 생성 | Policy Center와 관련 정책 상세 흐름에서 정책 확인 가능 |
| 4 | Shadow Mode 활성화 (security.zerotrust.mode=SHADOW) | 감사 로그에 인가 결정 표시 |
| 5 | SecurityFilterChain을 PlatformConfig로 교체 | 모든 Filter Chain이 성공적으로 빌드 |
| 6 | 정적 hasRole() / hasAuthority() 규칙 제거 | SecurityFilterChain 설정에 기존 hasRole() / hasAuthority() 요청 규칙이 남아 있지 않음 |
| 7 | Shadow Mode 감사 로그가 예상 동작과 일치하는지 확인 | 예상치 못한 거부/허용 결정 없음 |
| 8 | Enforce Mode로 전환 (security.zerotrust.mode=ENFORCE) | 애플리케이션이 Shadow Mode와 동일하게 동작 |
DSL API 레퍼런스
IdentityAuthDsl
인증 단계 인터페이스입니다. 각 메서드는 상태 선택을 위한 IdentityStateDsl을 반환합니다.
| 메서드 | 반환 타입 | 설명 |
|---|---|---|
global(SafeHttpCustomizer<HttpSecurity>) | IdentityAuthDsl | 모든 체인에 대한 공유 HTTP 보안 설정 |
form(Customizer<FormConfigurerConfigurer>) | IdentityStateDsl | 폼 로그인 등록 (기본 order: 100) |
rest(Customizer<RestConfigurerConfigurer>) | IdentityStateDsl | REST API 로그인 등록 (기본 order: 200) |
ott(Customizer<OttConfigurerConfigurer>) | IdentityStateDsl | One-Time Token 등록 (기본 order: 300) |
passkey(Customizer<PasskeyConfigurerConfigurer>) | IdentityStateDsl | Passkey/WebAuthn 등록 (기본 order: 400) |
mfa(Customizer<MfaDslConfigurer>) | IdentityStateDsl | 다중 인증 등록 (기본 order: 200) |
build() | PlatformConfig | 확정하고 불변 설정 생성 |
IdentityStateDsl
상태 관리 단계입니다. 인증 방법 등록 후 반환됩니다.
| 메서드 | 반환 타입 | 설명 |
|---|---|---|
session(Customizer<SessionStateConfigurer>) | IdentityAuthDsl | 세션 기반 상태 (자동: sessionFixation.changeSessionId) |
oauth2(Customizer<OAuth2StateConfigurer>) | IdentityAuthDsl | OAuth2/JWT 상태 (자동: Resource Server + Auth Server) |
application.yml 프로퍼티(로그인/로그아웃 URI, OAuth2 클라이언트 설정, 토큰 엔드포인트, MFA 설정)에 대해서는 상태 관리 및 Identity 설정을 참조하세요.