contexa-iam

권한 평가기

세밀한 메서드 레벨 접근 제어를 위한 도메인별 평가기가 포함된 Spring Security의 PermissionEvaluator를 구현하는 복합 권한 평가 시스템.

올바른 접근 방식 선택

접근 방식범위사용 사례
hasPermission() 객체 레벨 특정 도메인 객체에 대한 세밀한 CRUD 권한
hasRole() 전역 사용자 역할 기반의 광범위한 접근 제어
@Protectable 메서드 레벨 AI 통합이 포함된 동적 정책 기반 인가

빠른 시작

@PreAuthorize 어노테이션 또는 SpEL 정책 조건에서 hasPermission()을 사용하여 세밀한 권한을 확인합니다.

어노테이션에서 사용

Java
@PreAuthorize("hasPermission(#id, 'USER', 'READ')")
public User getUser(Long id) { ... }

@PreAuthorize("hasPermission(#id, 'ORDER', 'UPDATE')")
public void updateOrder(Long id, OrderDto dto) { ... }

정책 조건에서 사용

관리자 콘솔의 Policy Center에서 정책 조건을 작성할 때 SpEL 표현식 안에서 hasPermission()을 사용할 수 있습니다:

SpEL
hasPermission(#targetId, 'DOCUMENT', 'DELETE')
hasPermission(#targetId, 'REPORT', 'READ') and #ai.isAllowed()

CRUD 동의어 매핑

Permission Evaluator는 동의어 그룹을 사용하여 동등한 액션이 같은 권한으로 처리됩니다. 예를 들어 GET, FIND, READ, FETCH, VIEW, RETRIEVE는 모두 동일한 READ 그룹으로 해석됩니다.

권한 그룹동의어
READGET, FIND, READ, FETCH, VIEW, RETRIEVE
CREATECREATE, SAVE, ADD, INSERT, REGISTER, POST
UPDATEUPDATE, EDIT, MODIFY, CHANGE, PATCH, PUT
DELETEDELETE, REMOVE, DESTROY, DROP, ERASE, PURGE

즉, hasPermission(#id, 'USER', 'FETCH')hasPermission(#id, 'USER', 'READ')와 동등합니다.

아키텍처

권한 평가 시스템은 복합(Composite) 패턴을 따릅니다. CompositePermissionEvaluator가 진입점 역할을 하며, 권한 문자열 접두사 또는 대상 타입을 기반으로 hasPermission() 호출을 적절한 도메인별 평가기로 라우팅합니다.

Text
hasPermission(auth, target, 'USER_READ')
SpEL 표현식이 권한 평가를 트리거
CompositePermissionEvaluator
도메인 길이 순으로 정렬된 도메인 평가기로 라우팅 (가장 긴 것 우선)
UserPermissionEvaluator
domain: "USER"
RolePermissionEvaluator
domain: "ROLE"
RoleHierarchyPermissionEvaluator
domain: "ROLE_HIERARCHY"
GroupPermissionEvaluator
domain: "GROUP"
DomainPermissionEvaluator
domain: 사용자 정의
PermissionTargetPermissionEvaluator
domain: "PERMISSION"

CompositePermissionEvaluator

Spring Security의 PermissionEvaluator 인터페이스를 구현하고 권한 검사를 도메인별 평가기로 라우팅합니다.

Java
public class CompositePermissionEvaluator implements PermissionEvaluator {

    // Sorted by domain length (descending) for longest-prefix matching
    // e.g., ROLE_HIERARCHY matches before ROLE
    private final List<DomainPermissionEvaluator> evaluators;

    // Permission-string routing (e.g., "USER_READ" -> UserPermissionEvaluator)
    @Override
    public boolean hasPermission(Authentication authentication,
                                 Object targetDomainObject,
                                 Object permission) { ... }

    // Target-type routing (e.g., targetType="ROLE" -> RolePermissionEvaluator)
    @Override
    public boolean hasPermission(Authentication authentication,
                                 Serializable targetId,
                                 String targetType,
                                 Object permissionAction) { ... }

    // Entity resolution for UI display
    public Object resolveEntity(Serializable targetId, String targetType) { ... }
}

라우팅 로직

메서드 시그니처라우팅 전략
hasPermission(auth, target, permission) 주어진 권한 문자열에 대해 supportsPermission() 메서드가 true를 반환하는 첫 번째 평가기로 라우팅합니다. 권한은 도메인 접두사로 평가기에 매칭됩니다 (예: USER_READ는 User 평가기로 라우팅).
hasPermission(auth, targetId, targetType, action) 주어진 대상 타입에 대해 supportsTargetType() 메서드가 true를 반환하는 첫 번째 평가기로 라우팅합니다. 대상 타입 매칭은 대소문자를 구분하지 않습니다.

DomainPermissionEvaluator 인터페이스

Java
public interface DomainPermissionEvaluator {
    boolean supportsTargetType(String targetType);
    boolean supportsPermission(String permission);
    boolean hasPermission(Authentication auth, Object target, Object permission);
    boolean hasPermission(Authentication auth, Serializable targetId,
                          String targetType, Object permission);
    Object resolveEntity(Serializable targetId);
}

AbstractDomainPermissionEvaluator

모든 도메인 평가기를 위한 공통 기능을 제공하는 기본 클래스입니다. CRUD 동의어 해석 및 PermissionAuthority 기반 권한 매칭을 구현합니다.

권한 매칭

권한 문자열은 사용자의 PermissionAuthority 부여에 대해 매칭됩니다. 매칭 프로세스:

  1. 도메인 접두사 제거 (예: USER_READREAD)
  2. 액션 동사에 대한 CRUD 동의어 해석
  3. 대상 타입 METHOD이면서 액션 동의어와 도메인 이름을 모두 포함하는 권한에 대해 매칭

CRUD 동의어 그룹

그룹동의어
ReadGET, FIND, READ, FETCH, VIEW, RETRIEVE, LIST, SEARCH
CreateCREATE, SAVE, ADD, INSERT, REGISTER, POST
UpdateUPDATE, EDIT, MODIFY, CHANGE, PATCH, PUT
DeleteDELETE, REMOVE, DESTROY, DROP, ERASE, PURGE, CLEAR, TRUNCATE

예를 들어, hasPermission(auth, null, 'USER_FETCH')는 사용자가 USERGET, FIND, READ, FETCH, VIEW, RETRIEVE, LIST, SEARCH 중 하나를 포함하는 권한을 가지고 있으면 매칭됩니다.

엔티티 해석

각 평가기는 ApplicationContext에서 이름이 지정된 리포지토리 빈을 통해 엔티티를 해석합니다. resolveEntity() 메서드는 리플렉션을 사용하여 해석된 리포지토리에서 findById()를 호출합니다.

도메인 평가기

평가기도메인리포지토리 빈권한 접두사
UserPermissionEvaluator USER userRepository USER_*
RolePermissionEvaluator ROLE roleRepository ROLE_*
RoleHierarchyPermissionEvaluator ROLE_HIERARCHY roleHierarchyRepository ROLE_HIERARCHY_*
GroupPermissionEvaluator GROUP groupRepository GROUP_*
PermissionTargetPermissionEvaluator PERMISSION permissionRepository PERMISSION_*

모든 평가기는 AbstractDomainPermissionEvaluator를 확장하며 domain(), repositoryBeanName(), getApplicationContext() 메서드만 정의합니다. 권한 검사 로직은 추상 기본 클래스에서 상속됩니다.

평가 순서

평가기는 도메인 문자열 길이의 내림차순으로 정렬됩니다. 이를 통해 더 구체적인 도메인이 먼저 매칭됩니다:

  1. ROLE_HIERARCHY (14자) — 가장 먼저 확인
  2. PERMISSION (10자)
  3. GROUP (5자)
  4. USER (4자)
  5. ROLE (4자)

이 순서는 ROLE_HIERARCHY_READROLE 평가기에 의해 잘못 매칭되는 것을 방지합니다.

SpEL 표현식에서의 사용

Java
// Permission-string based (routed by prefix)
@PreAuthorize("hasPermission(null, 'USER_READ')")
public List<UserDto> getUsers() { ... }

// Target-type based (routed by targetType parameter)
@PreAuthorize("hasPermission(#id, 'ROLE', 'EDIT')")
public void updateRole(@PathVariable Long id, @RequestBody RoleDto dto) { ... }

// Group-scoped permission
@PreAuthorize("hasPermission(#groupId, 'GROUP', 'DELETE')")
public void deleteGroup(@PathVariable Long groupId) { ... }

관리자 콘솔을 통한 권한 관리

관리자 콘솔 UI를 통해 권한을 생성하고 관리할 수 있으며, CompositePermissionEvaluator 라우팅 시스템과 통합되는 세밀한 접근 제어를 정의하기 위한 시각적 인터페이스를 제공합니다.

권한 관리로 이동

Admin > ID & Access Management > Permission Management로 이동합니다. 이 뷰는 대상 타입, 액션 타입, 연관된 역할과 함께 정의된 모든 권한을 나열합니다.

새 권한 생성

"Create Permission"을 클릭하고 다음 필드를 입력합니다:

필드설명예시
Name 일반적으로 DOMAIN_ACTION 형식의 고유한 권한 식별자 ORDER_UPDATE
Description 이 권한이 무엇을 부여하는지에 대한 사람이 읽을 수 있는 설명 "기존 고객 주문 수정을 허용"
Target Type 리소스 범위: 엔드포인트 레벨은 URL, 메서드 레벨 권한은 METHOD METHOD
Action Type CRUD 액션: READ, CREATE, UPDATE, DELETE, 또는 사용자 정의 액션 UPDATE

역할에 권한 연결

권한을 생성한 후 역할 관리 섹션으로 이동하여 연결합니다. 대상 역할을 열고 "Permissions" 탭으로 이동하여 새로 생성한 권한을 추가합니다. 해당 역할을 가진 사용자가 hasPermission()을 호출하면 CompositePermissionEvaluator가 사용자의 PermissionAuthority 부여에 매칭되는 도메인과 액션이 포함되어 있는지 확인합니다.

권한이 DomainPermissionEvaluator 라우팅에 연결되는 방식

관리자 콘솔에서 생성된 권한은 사용자의 인증 토큰에 PermissionAuthority 부여로 저장됩니다. hasPermission()이 호출되면 CompositePermissionEvaluator가 도메인 접두사를 기반으로 적절한 DomainPermissionEvaluator에 호출을 라우팅합니다. 평가기는 사용자의 권한에 올바른 액션 동의어 그룹과 매칭되는 권한이 포함되어 있는지 확인합니다.

Policy Center에서 hasPermission() 사용

Policy Center에서 정책을 생성할 때, hasPermission()을 SpEL 조건 표현식 내에서 사용하여 권한 기반 접근 제어를 다른 인가 검사와 결합할 수 있습니다.

중요: hasPermission()URL 레벨 정책에서 제거됩니다. 메서드 레벨 정책(즉, @Protectable 메서드와 같은 METHOD 리소스를 대상으로 하는 정책)에서만 작동합니다. URL 레벨 정책 조건에 hasPermission()을 포함하면 평가 중 무시됩니다.

정책 조건 예제

SpEL
// Permission check only
hasPermission(#targetId, 'DOCUMENT', 'READ')

// Combined with AI security expression
hasPermission(#targetId, 'ORDER', 'UPDATE') and #ai.isAllowed()

// Combined with role check
hasPermission(#targetId, 'REPORT', 'DELETE') or hasRole('ADMIN')

// Combined with time-based condition
hasPermission(#targetId, 'PAYMENT', 'CREATE') and #time.isBusinessHours()

현재 OSS 기준으로 이 표현식은 Policy Center의 조건 입력 필드에서 사용합니다. 대상 리소스 타입이 METHOD인 경우 서버 쪽 권한 평가기와 정책 변환 로직에 맞는 조건을 직접 검토해야 합니다.

사용자 정의 도메인 평가기 생성

자체 도메인 타입을 위해 DomainPermissionEvaluator 인터페이스를 구현하여 권한 평가 시스템을 확장할 수 있습니다. 사용자 정의 평가기는 Spring 빈으로 등록되면 CompositePermissionEvaluator에 의해 자동으로 감지됩니다.

구현 단계

  1. DomainPermissionEvaluator 인터페이스 구현
  2. 도메인 이름을 반환하도록 domain() 정의 (예: "DOCUMENT")
  3. 도메인 엔티티를 처리하는 리포지토리의 Spring 빈 이름을 반환하도록 repositoryBeanName() 정의
  4. 클래스를 Spring @Component로 등록 — CompositePermissionEvaluator가 애플리케이션 컨텍스트의 모든 DomainPermissionEvaluator 빈을 자동 감지

구현 예제

Java
@Component
public class DocumentPermissionEvaluator
        extends AbstractDomainPermissionEvaluator {

    private final ApplicationContext applicationContext;

    public DocumentPermissionEvaluator(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public String domain() {
        return "DOCUMENT";
    }

    @Override
    public String repositoryBeanName() {
        return "documentRepository";
    }

    @Override
    protected ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

이 빈이 등록되면 hasPermission(#id, 'DOCUMENT', 'READ')이 자동으로 DocumentPermissionEvaluator로 라우팅됩니다. 기본 클래스 AbstractDomainPermissionEvaluator가 CRUD 동의어 해석, PermissionAuthority 매칭, 이름이 지정된 리포지토리 빈을 통한 엔티티 해석을 처리합니다.

자동 감지 및 순서

CompositePermissionEvaluator는 시작 시 모든 DomainPermissionEvaluator 빈을 수집하고 도메인 문자열 길이순(가장 긴 것 우선)으로 정렬합니다. 이를 통해 도메인 "DOCUMENT_ARCHIVE"의 사용자 정의 평가기가 도메인 "DOCUMENT"의 평가기보다 먼저 매칭됩니다. 빈 등록 외에 추가 구성은 필요 없습니다.