인가 아키텍처
URL 및 메서드 수준에서 동적 정책 적용을 지원하는 XACML 기반 인가 아키텍처. AI 기반 보안 표현식과 런타임 정책 관리를 제공하며, 코드 변경이나 재배포 없이 모든 것을 관리할 수 있습니다.
개요
기존 Spring Security 인가는 컴파일 타임에 하드코딩되는 @PreAuthorize("hasRole('ADMIN')")과 같은 정적 어노테이션을 사용합니다. 비즈니스 요구사항이 변경될 때 — 예를 들어 관리자 접근을 업무 시간으로 제한하거나 민감한 작업에 MFA를 요구하는 경우 — 소스 코드를 수정하고 재배포해야 합니다.
Contexa의 인가 아키텍처는 인가 정책을 데이터베이스로 외부화하여 이 문제를 해결합니다. 정책은 관리자 콘솔을 통해 관리되며, 현재 OSS 기본 작성 화면은 Policy Center입니다. 지원되는 정책 변경은 재배포 없이 런타임에 반영됩니다. 엔진은 XACML 표준 아키텍처를 따르며, 정책 적용, 의사결정, 관리, 정보 수집, 검색을 각각 독립적이고 테스트 가능한 관심사로 분리하는 다섯 가지 구성 요소로 이루어져 있습니다.
이러한 구성 요소가 Spring Security 필터 체인에 어떻게 연결되는지에 대한 자세한 내용은 Identity DSL을 참조하세요.
정적 인가 vs 동적 인가
| 구분 | 정적 (Spring Security 기본) | 동적 (Contexa) |
|---|---|---|
| 정책 정의 | 소스 코드의 어노테이션 | Admin UI를 통한 데이터베이스 저장 정책 |
| 정책 변경 | 코드 변경 + 재배포 필요 | 런타임 핫 리로드, 무중단 |
| 적용 범위 | 메서드별 또는 URL별 (컴파일 타임) | URL 수준 + 메서드 수준 (런타임) |
| 컨텍스트 인식 | 인증 + 권한만 | 사용자, 리소스, 환경, 행동 메트릭, AI 위협 점수 |
| AI 통합 | 불가 | SpEL에서 #ai.isAllowed(), #ai.needsChallenge() |
| 감사 추적 | 수동 구현 필요 | CentralAuditFacade를 통한 정책 서비스 이벤트 및 거부된 URL 인가 결과 기록 |
| 공존 | 둘 다 함께 사용 가능: 정적 규칙은 .requestMatchers().permitAll(), 동적 규칙은 .anyRequest().access(customDynamicAuthorizationManager) | |
주요 기능
- 런타임 정책 변경 — 관리자 콘솔의 Policy Center 또는 서버 측 정책 API를 통해 재배포 없이 접근 규칙을 추가, 수정 또는 취소
- 이중 적용 레이어 — 동적 인가를 통한 URL 수준 및 @Protectable을 통한 메서드 수준
- 풍부한 컨텍스트 평가 — 정책은 PIP가 수집한 행동 메트릭, 시간대, 기기 정보, AI 위협 점수를 참조할 수 있음
- AI 기반 보안 표현식 — SpEL 정책 조건에서
#ai.isAllowed(),#ai.needsChallenge()및 기타 AI 표현식을 직접 사용 - 엔드투엔드 리소스 관리 — 리소스 스캐너가 엔드포인트를 발견하고, Policy Center 중심 UI에서 권한 정의와 정책 생성을 수행
- SpEL 네이티브 — XML 대신 Spring Expression Language를 사용하여 익숙한 Spring Security 프로그래밍 모델 유지
- 동적 인가 — URL 수준 적용 상세
- @Protectable — 메서드 수준 적용
- 정책 관리 — 정책 수명주기 및 Policy Center 작성 흐름
- 리소스 스캐너 — 자동 리소스 검색 및 엔드투엔드 워크플로
아키텍처 개요
Contexa는 SpEL(Spring Expression Language)을 정책 표현 언어로 사용하여 XACML 참조 아키텍처를 구현합니다. XML 기반 XACML 정책 대신 정책, 규칙, 조건, 대상이 SpEL 표현식과 함께 JPA 엔티티로 저장되는 관계형 데이터 모델을 사용합니다.
PEP — 정책 적용 지점(Policy Enforcement Point)
PEP는 인가 요청을 가로채고 결정을 PDP에 위임합니다. Contexa는 URL 수준 및 메서드 수준 적용을 위한 두 가지 PEP 구현을 제공합니다.
CustomDynamicAuthorizationManager
URL 기반 정책 적용을 위해 AuthorizationManager<RequestAuthorizationContext>를 구현합니다. 애플리케이션 컨텍스트 갱신 시 PRP에서 모든 URL 정책을 로드하고 RequestMatcherEntry 매핑 목록을 생성합니다.
public class CustomDynamicAuthorizationManager
implements AuthorizationManager<RequestAuthorizationContext> {
private final PolicyRetrievalPoint policyRetrievalPoint;
private final ExpressionAuthorizationManagerResolver managerResolver;
private final ContextHandler contextHandler;
private final ZeroTrustEventPublisher zeroTrustEventPublisher;
private final AuthorizationMetrics metricsCollector;
// Initializes on ContextRefreshedEvent
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
initialize();
}
@Override
public AuthorizationDecision check(
Supplier<Authentication> authenticationSupplier,
RequestAuthorizationContext context) { ... }
// Hot-reload: clears PRP cache and re-initializes mappings
public synchronized void reload() { ... }
}
정책-표현식 변환
getExpressionFromPolicy() 메서드는 Policy 엔티티를 SpEL 표현식 문자열로 변환합니다:
- ALLOW 효과의 조건 없음 →
permitAll - DENY 효과의 조건 없음 →
denyAll - 단일 조건 → 표현식을 직접 사용
- 여러 단순 권한 조건 →
hasAnyAuthority('A','B') - 여러 혼합 조건 →
or로 결합 - DENY 효과 → 표현식을
!()로 래핑 hasPermission()호출은 메서드 수준 평가에만 해당하므로 URL 정책에서 제거
ExpressionAuthorizationManagerResolver
SpEL 표현식 문자열을 ExpressionEvaluator 구현의 체인에 위임하여 AuthorizationManager 인스턴스로 해석합니다.
public class ExpressionAuthorizationManagerResolver {
private final List<ExpressionEvaluator> evaluators;
private final SecurityExpressionHandler<RequestAuthorizationContext>
customWebSecurityExpressionHandler;
public AuthorizationManager<RequestAuthorizationContext> resolve(
String expression) {
// Iterates evaluators; WebSpelExpressionEvaluator creates
// WebExpressionAuthorizationManager with custom handler
}
}
PDP — 정책 결정 지점(Policy Decision Point)
PDP는 정책에 대한 인가 요청을 평가하고 결정을 반환합니다. Contexa에서 런타임 URL/메서드 평가는 SpEL 표현식 처리로 수행되며, PolicyExpressionConverter는 URL 정책을 런타임 표현식으로 정규화하고 PolicyTranslator는 사람이 읽을 수 있는 설명과 엔타이틀먼트 표현을 담당합니다.
PolicyTranslator
SpEL AST(추상 구문 트리)를 순회하여 Policy 엔티티를 사람이 읽을 수 있는 문자열 및 EntitlementDto 객체로 변환합니다. 사용자 정의 SpEL 메서드 번역을 위한 플러그형 SpelFunctionTranslator 인스턴스를 지원합니다.
public class PolicyTranslator {
private final RoleRepository roleRepository;
private final GroupRepository groupRepository;
private final PermissionRepository permissionRepository;
private final List<SpelFunctionTranslator> translators;
// Policy -> Human readable string
public String translatePolicyToString(Policy policy) { ... }
// Policy -> Entitlement DTOs with subject/action/condition analysis
public Stream<EntitlementDto> translate(Policy policy, String resourceName) { ... }
// Policy -> ExpressionNode tree for programmatic analysis
public ExpressionNode parsePolicy(Policy policy) { ... }
// Single condition -> ExpressionNode
public ExpressionNode parseCondition(PolicyCondition condition) { ... }
}
SpEL AST 노드 변환
| SpEL 노드 | 변환 결과 |
|---|---|
OpAnd | (left and right) |
OpOr | (left or right) |
OperatorNot | NOT (expr) |
OpEQ | left equals right |
MethodReference | 일치하는 SpelFunctionTranslator에 위임 |
Identifier("permitAll") | All access permitted |
Identifier("denyAll") | All access denied |
PAP — 정책 관리 지점(Policy Administration Point)
PAP는 인가 정책의 수명주기를 관리합니다. 모든 PAP 서비스의 상세 API 문서는 정책 관리 페이지를 참조하세요.
| 서비스 | 책임 |
|---|---|
PolicyService |
정책 CRUD 작업을 관리하고 역할-권한 할당이 변경될 때 정책을 동기화합니다. |
PolicyBuilderService |
정책 작성 서비스에서 사용하는 템플릿 조회, 시각 모델 변환, 충돌 감지를 제공합니다. 현재 OSS 기본 작성 UI는 독립 빌더 템플릿보다 Policy Center에 더 가깝습니다. |
PolicySynchronizationService |
RolePermissionsChangedEvent가 발행되면 자동으로 정책을 재생성하고 업데이트합니다. |
PolicyEnrichmentService |
PolicyTranslator에 위임하여 사람이 읽을 수 있는 설명으로 정책을 보강합니다. |
BusinessPolicyService |
비즈니스 수준의 규칙을 PDP가 평가할 수 있는 기술적 정책 표현으로 변환합니다. |
PIP — 정책 정보 지점(Policy Information Point)
PIP는 정책 평가에 필요한 속성을 수집합니다. 주체, 리소스, 작업, 환경에 대한 컨텍스트 데이터를 PDP에 제공합니다.
AttributeInformationPoint
public interface AttributeInformationPoint {
Map<String, Object> getAttributes(AuthorizationContext context);
}
DatabaseAttributePIP
데이터베이스 소스에서 인가 컨텍스트를 보강하는 기본 PIP 구현입니다. 다섯 가지 범주의 속성을 수집합니다:
| 범주 | 수집 속성 |
|---|---|
| 기본 사용자 | userId, userEmail, userGroups, groupCount, mfaEnabled, createdAt |
| 행동 메트릭 | requestsInLastHour, requestsInLastDay, uniqueResourcesAccessed, typicalAccessHours, accessVelocity |
| 리소스 패턴 | resourceTotalAccess, resourceUniqueUsers, resourceSensitivityLevel, resourceRecentFailures |
| 시간/환경 | currentHour, currentDayOfWeek, isBusinessHours, isWeekend, remoteAddress |
| 보안 프로필 | userSecurityScore, hasRecentFailures, highAccessVelocity, unusualAccessTime, riskIndicatorCount |
AuthorizationContext
public record AuthorizationContext(
Authentication subject,
Users subjectEntity,
ResourceDetails resource,
String action,
EnvironmentDetails environment,
Map<String, Object> attributes
) {}
ContextHandler
URL 및 메서드 수준 인가 모두를 위한 AuthorizationContext 인스턴스를 생성합니다:
public interface ContextHandler {
AuthorizationContext create(Authentication authentication,
HttpServletRequest request);
AuthorizationContext create(Authentication authentication,
MethodInvocation invocation);
}
PRP — 정책 검색 지점(Policy Retrieval Point)
PRP는 기본 데이터 저장소에서 정책을 검색합니다. Contexa는 고처리량 인가 시 데이터베이스 쿼리를 최소화하기 위해 ContexaCacheService를 통해 정책을 캐싱합니다.
public interface PolicyRetrievalPoint {
List<Policy> findUrlPolicies();
void clearUrlPoliciesCache();
List<Policy> findMethodPolicies(String methodIdentifier);
void clearMethodPoliciesCache();
}
DatabasePolicyRetrievalPoint
ContexaCacheService를 통한 캐싱과 함께 PolicyRepository를 통해 데이터베이스에서 정책을 검색하는 기본 구현입니다.
| 캐시 키 | 설명 |
|---|---|
policies:url:all | 모든 활성 URL 정책 |
policies:method:{identifier} | 메서드 식별자별 메서드 특정 정책 |
AI 생성 정책은 approvalStatus가 APPROVED이고 isActive가 true인 경우에만 포함됩니다. 미승인 AI 정책은 PEP의 초기화 단계에서 필터링됩니다.
전체 인가 흐름
1. 요청이 Spring Security 필터 체인에 도착
2. PEP (CustomDynamicAuthorizationManager)가 RequestAuthorizationContext를 수신
3. ContextHandler가 주체, 리소스, 작업으로 AuthorizationContext를 생성
4. PEP가 캐싱된 RequestMatcherEntry 목록에서 요청 URL을 매칭
5. 매칭된 항목의 AuthorizationManager가 SpEL 표현식을 평가
- 표현식은 Policy -> Rules -> Conditions에서 파생
- hasRole(), hasAuthority(), hasAnyAuthority() 등
6. PDP (SpEL 평가기)가 AuthorizationDecision(granted/denied)을 반환
7. PEP가 결정을 적용
- 허용: 요청이 컨트롤러로 진행
- 거부: 403 Forbidden 응답
8. 거부된 URL 인가 결과와 정책 서비스 이벤트가 CentralAuditFacade 기반 감사 흐름에 기록
AI 기반 보안 표현식
Contexa는 AI 기반 표현식으로 표준 Spring Security SpEL 환경을 확장합니다. 이들은 AbstractAISecurityExpressionRoot가 지원하는 #ai 변수를 통해 URL 수준 및 메서드 수준 정책 조건 모두에서 사용할 수 있습니다.
AI 표현식 레퍼런스
모든 AI 표현식은 현재 요청에 대한 Zero Trust 액션을 조회합니다. 현재 OSS 기준으로 메서드 레벨 평가는 요청 attribute를 먼저 확인하고, 그다음 짧은 수명의 Caffeine 로컬 캐시를 확인한 뒤, 마지막으로 공유 ZeroTrustActionRepository를 조회합니다.
| 표현식 | 반환 타입 | 설명 |
|---|---|---|
#ai.isAllowed() | boolean | AI Zero Trust 액션이 ALLOW인 경우 true 반환 |
#ai.isBlocked() | boolean | AI Zero Trust 액션이 BLOCK인 경우 true 반환 |
#ai.needsChallenge() | boolean | AI가 단계별 인증 챌린지를 권장하는 경우 true 반환 |
#ai.needsEscalation() | boolean | AI가 사람 검토자에게 에스컬레이션을 권장하는 경우 true 반환 |
#ai.isPendingAnalysis() | boolean | AI 분석이 아직 완료되지 않은 경우 true 반환 |
#ai.hasAction('ACTION') | boolean | Zero Trust 액션이 주어진 문자열과 일치하면 true 반환 |
#ai.hasActionIn('A','B') | boolean | Zero Trust 액션이 주어진 값 중 하나와 일치하면 true 반환 |
#ai.hasActionOrDefault('DEFAULT','A','B') | boolean | hasActionIn과 동일하나, 분석 결과가 없을 때 기본 액션을 사용 |
SpEL 정책 조건에서의 사용
// Require both role and AI approval
hasRole('ADMIN') and #ai.isAllowed()
// Allow if user has role OR AI allows it
hasRole('MANAGER') or #ai.isAllowed()
// Block if AI detects threat, regardless of role
hasRole('USER') and !#ai.isBlocked()
// Flexible action matching with fallback
hasRole('USER') and #ai.hasActionOrDefault('ALLOW', 'ALLOW', 'CHALLENGE')
URL 수준 표현식
CustomWebSecurityExpressionRoot는 URL 정책 조건에서 사용할 수 있는 HTTP 전용 표현식을 추가합니다:
| 표현식 | 설명 |
|---|---|
hasIpAddress('192.168.1.0/24') | 요청이 지정된 IP 주소 또는 CIDR 범위에서 발생했는지 확인 |
getHttpMethod() | 요청의 HTTP 메서드를 반환 (GET, POST, PUT, DELETE 등) |
메서드 수준 표현식
CustomMethodSecurityExpressionRoot는 로컬 캐싱과 함께 메서드별 표현식을 추가합니다:
hasPermission(target, permission)—ownerField가 설정된 경우 소유권 검증을 포함하여 CompositePermissionEvaluator를 통해 권한을 평가hasPermission(targetId, targetType, permission)— 동일한 소유권 지원의 ID 기반 권한 평가- 로컬 캐시 — Redis 조회를 최소화하기 위해 5초 TTL과 최대 10,000개 항목의 Caffeine을 사용하여 Zero Trust 액션 결과를 로컬에 캐싱
AbstractAISecurityExpressionRoot— URL 및 메서드 컨텍스트 모두에 AI 표현식(#ai.*)을 제공CustomWebSecurityExpressionRoot— URL 정책을 위한hasIpAddress()추가CustomMethodSecurityExpressionRoot— 메서드 정책을 위한 소유권 검증이 포함된hasPermission()추가
빠른 시작
네 단계로 동적 인가를 실행하세요:
1. Spring Security 설정
CustomDynamicAuthorizationManager를 주입하고 보안 필터 체인에 추가합니다:
@Configuration
public class SecurityConfig {
@Autowired
private CustomDynamicAuthorizationManager dynamicAuthManager;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
// Static rules evaluated first
.requestMatchers("/", "/home", "/css/**", "/js/**").permitAll()
.requestMatchers("/login", "/register").permitAll()
// All other requests use dynamic authorization
.anyRequest().access(dynamicAuthManager)
);
return http.build();
}
}
2. 첫 번째 정책 생성
관리자 콘솔에서 Policy Center를 열고, Create 표면에서 URL 대상(예: /admin/**)을 ALLOW 효과와 조건 hasRole('ADMIN')으로 설정합니다.
3. 메서드 보호
@Service
public class OrderService {
@PreAuthorize("hasRole('ROLE_USER')")
public Order getOrder(Long orderId) {
return orderRepository.findById(orderId).orElseThrow();
}
}
4. 확인
보호된 URL 또는 메서드에 접근합니다. 정책 서비스 이벤트와 거부된 URL 인가 결과는 관리자 감사 및 모니터링 화면에서 확인할 수 있습니다. 정책을 변경하려면 Policy Center 또는 서버 측 정책 흐름에서 업데이트하면 되고, 런타임 인가 매핑이 다시 로드되어 재배포 없이 반영됩니다.
- 동적 인가 — 상세한 URL 수준 설정
- @Protectable — 메서드 수준 보호
- 리소스 스캐너 — 자동 리소스 검색 및 정책 생성
관리자 콘솔 시작하기
Contexa 관리자 콘솔은 전체 인가 수명주기를 관리하기 위한 시각적 인터페이스를 제공합니다. admin 모듈을 활성화한 후 /admin/dashboard에서 접근할 수 있습니다.
핵심 인가 워크플로
관리자 콘솔은 인가 정책을 구축하고 관리하기 위한 세 가지 주요 워크플로를 제공합니다:
- Policy Center 리소스 흐름 — 보호 대상 리소스를 검토하고 정의합니다. 현재 OSS UI는 Policy Center를 중심으로 리소스 그룹화와 권한 정의를 수행합니다.
- 정책 생성 흐름 — Policy Center와 정책 저장 경로를 통해 인가 정책을 생성합니다. 대상, 조건, 효과를 구성하고 필요 시 AI 진단/추천 결과를 반영할 수 있습니다.
- 실시간 보안 현황 — 정책 적용을 실시간으로 모니터링합니다. 감사 로그를 검토하여 인가 결정이 기대에 부합하는지 확인하고, 대시보드로 보안 가시성을 확보합니다.
전체 관리자 콘솔 레퍼런스는 관리자 콘솔 문서를 참조하세요. 리소스 검색에서 정책 테스트까지의 단계별 안내는 엔드투엔드 워크플로 가이드를 참조하세요.