Domain Object Security (ACLs)

김상욱·2024년 12월 11일

이 섹션에서 설명하는 내용은 Spring Security를 사용해 도메인 객체의 보안을 관리하는 방법, 특히 Access Control Lists(ACLs)을 활용하는 방법입니다. 간단히 말해, 이 기능은 개별 객체에 대해 더 세부적인 권한 제어를 가능하게 합니다.

1. 도메인 객체 보안이 필요한 이유

기본적인 애플리케이션 보안은 일반적으로 웹 요청(request)이나 메서드 호출(method invocation) 수준에서 이루어집니다. 예를 들어, 특정 사용자가 특정 URL에 접근할 수 있는지, 또는 특정 서비스 메서드를 호출할 수 있는지 확인하는 방식입니다. 하지만 복잡한 애플리케이션에서는 단순히 누가(Authentication) 요청을 보냈는지뿐만 아니라, 어떤 객체(SomeDomainObject)에 접근하려는지를 함께 고려해야 할 경우가 많습니다.

예시: 애완동물 클리닉 애플리케이션

  • 직원: 모든 데이터를 볼 수 있는 권한이 있어야 합니다.
  • 고객: 자신의 고객 기록만 볼 수 있어야 합니다. 하지만 고객이 원한다면, 다른 사용자(예: 강아지 유치원 멘토)에게 자신의 기록을 공유할 수 있습니다.

2. 가능한 접근 방법

Spring Security를 사용하면, 위의 시나리오를 해결할 수 있는 몇 가지 구현 방법이 있습니다:

(1) 비즈니스 메서드 내에서 보안을 구현

  • 고객 도메인 객체(Customer)의 속성 중 허가된 사용자 목록을 확인하여 접근 권한을 확인합니다.
  • SecurityContextHolder.getContext().getAuthentication()를 사용해 현재 사용자의 인증 정보(Authentication 객체)를 가져와 확인합니다.
문제점
  • 비즈니스 코드와 권한 확인 코드가 결합됨: 단위 테스트가 어려워지고, 권한 확인 로직을 재사용하기 어렵습니다.
  • 유지보수성 저하: 비즈니스 로직이 변경될 경우, 보안 로직도 수정해야 할 가능성이 높습니다.

(2) AccessDecisionVoter를 사용해 권한을 확인 (GrantedAuthority 기반)

  • Authentication 객체 내부에 저장된 GrantedAuthority[]를 기반으로 권한을 확인합니다.
  • 즉, 각 사용자에게 허용된 고객(Customer) 객체 정보를 GrantedAuthority로 저장해둡니다.
문제점
  • GrantedAuthority에 많은 데이터를 저장하는 경우 비효율적: 사용자가 접근할 수 있는 객체 수가 많아지면 메모리 사용량이 증가합니다.
  • 확장성 문제: 고객 객체 수가 수천 개 이상일 경우, Authentication 객체를 생성하는 데 소요되는 메모리와 시간이 지나치게 커질 수 있습니다.

(3) AccessDecisionVoter에서 도메인 객체를 직접 접근

  • Voter가 DAO(Data Access Object)에 직접 접근해 고객(Customer) 객체를 가져옵니다.
  • 가져온 객체에서 허가된 사용자 목록을 확인해 권한을 결정합니다.
장점
  • 권한 로직과 비즈니스 로직 분리: 권한 확인 로직이 비즈니스 코드와 분리되므로 재사용성과 유지보수성이 향상됩니다.
문제점
  • 비효율적인 데이터베이스 호출: 한 번의 메서드 호출에 대해 DAO를 두 번 호출해야 하는 경우가 많습니다(AccessDecisionVoter와 비즈니스 메서드 각각에서 호출).
  • 복잡성 증가: 여전히 ACL(Access Control List) 관련 로직을 직접 작성해야 하므로 개발 부담이 커질 수 있습니다.

3. 위 접근 방법들의 공통 문제점

  • 권한 로직 구현 부담: 모든 방식에서 ACL을 위한 데이터 저장 및 비즈니스 로직을 직접 작성해야 합니다.
  • 성능 문제: 특히 객체 수가 많아지거나 데이터베이스 접근이 자주 필요한 경우, 메모리 사용량과 응답 시간이 문제로 이어질 수 있습니다.

4. 대안: Spring Security의 ACL 지원

위의 접근 방식들은 각각 장점과 단점이 있지만, 모두 어느 정도의 비효율성과 구현 부담을 동반합니다. 따라서, Spring Security는 자체적으로 ACL을 지원하여 이러한 문제를 해결하는 방법을 제공합니다.

Spring Security의 ACL은 다음과 같은 이점을 제공합니다:

  • 재사용 가능성: 별도의 로직을 작성하지 않고도 ACL 규칙을 정의하고 사용할 수 있습니다.
  • 확장성: 대규모 데이터를 처리할 때 효율적인 방식으로 권한을 관리할 수 있습니다.
  • 관심사의 분리: 비즈니스 로직과 권한 확인 로직이 명확히 분리됩니다.

Spring Security의 ACL에 대한 더 구체적인 설명은 다음 섹션에서 다뤄질 것입니다.

결론

위 설명은 도메인 객체에 대해 권한을 부여하거나 제한할 때 Spring Security에서 선택할 수 있는 다양한 접근 방식을 비교한 것입니다. 각 방법은 상황에 따라 적합할 수 있지만, 일반적으로 효율성과 유지보수성을 고려하면 Spring Security의 기본 ACL 지원 기능을 사용하는 것이 더 나은 선택일 가능성이 높습니다.


핵심 개념

Spring Security의 ACL 서비스는 spring-security-acl-xxx.jar에 포함되어 있습니다. 도메인 객체 인스턴스 보안 기능을 사용하려면 이 JAR 파일을 클래스패스에 추가해야 합니다.

Spring Security의 도메인 객체 인스턴스 보안 기능은 Access Control List(ACL) 개념에 기반합니다. 시스템 내 모든 도메인 객체 인스턴스는 고유한 ACL을 가지며, 해당 ACL에는 해당 도메인 객체를 사용할 수 있는 사용자와 사용할 수 없는 사용자에 대한 세부 정보가 기록됩니다. 이를 통해 Spring Security는 애플리케이션에 다음과 같은 주요 ACL 관련 기능을 제공합니다:

  1. 모든 도메인 객체에 대한 ACL 항목을 효율적으로 검색하고 수정하는 기능
  2. 메서드 호출 전에 주어진 주체(principal)가 객체를 사용할 수 있는지 확인하는 기능
  3. 메서드 호출 후에도 주어진 주체가 객체(또는 반환된 객체)를 사용할 수 있는지 확인하는 기능

첫 번째 항목에서 언급했듯이, Spring Security ACL 모듈의 주요 기능 중 하나는 ACL을 고성능으로 검색하는 방법을 제공하는 것입니다. 이 ACL 저장소 기능은 매우 중요합니다. 왜냐하면 시스템 내 모든 도메인 객체 인스턴스가 여러 액세스 제어 항목(Access Control Entry)을 가질 수 있고, 각 ACL이 트리 구조로 다른 ACL을 상속받을 수 있기 때문입니다(이는 Spring Security에서 지원하며 매우 일반적으로 사용됩니다).

Spring Security의 ACL 기능은 다음과 같은 점을 고려하여 설계되었습니다:

  • 고성능 ACL 검색
  • 플러그형 캐싱 지원
  • 교착 상태를 최소화한 데이터베이스 업데이트
  • ORM 프레임워크와 독립적 설계 (JDBC 직접 사용)
  • 적절한 캡슐화 및 투명한 데이터베이스 업데이트

기본 데이터베이스 테이블

ACL 모듈은 데이터베이스 중심으로 동작하며, 기본 구현에서 사용되는 주요 테이블 4개를 살펴보겠습니다. 일반적인 Spring Security ACL 배포에서 테이블 크기 순서로 나열됩니다(가장 많은 행을 가진 테이블이 마지막):

  1. ACL_SID

    • 시스템 내 모든 주체(principal) 또는 권한(GrantedAuthority)을 고유하게 식별합니다.
    • "SID"는 Security Identity의 약자입니다.
    • 주요 컬럼: ID, SID의 텍스트 표현, 해당 텍스트 표현이 주체 이름인지 권한(GrantedAuthority)인지 나타내는 플래그.
    • 각 고유 주체 또는 권한에 대해 단일 행이 존재합니다.
  2. ACL_CLASS

    • 시스템 내 도메인 객체 클래스(Class)를 고유하게 식별합니다.
    • 주요 컬럼: ID, Java 클래스 이름.
    • ACL 권한을 저장하려는 각 고유 클래스에 대해 단일 행이 존재합니다.
  3. ACL_OBJECT_IDENTITY

    • 시스템 내 각 고유 도메인 객체 인스턴스에 대한 정보를 저장합니다.
    • 주요 컬럼: ID, ACL_CLASS 테이블의 외래 키, 고유 식별자, 부모, ACL_SID 테이블의 외래 키(도메인 객체 소유자), ACL 항목이 부모 ACL을 상속받을 수 있는지 여부.
    • ACL 권한을 저장하려는 각 도메인 객체 인스턴스에 대해 단일 행이 존재합니다.
  4. ACL_ENTRY

    • 각 수신자(recipient)에게 할당된 개별 권한을 저장합니다.
    • 주요 컬럼: ACL_OBJECT_IDENTITY에 대한 외래 키, 수신자(ACL_SID에 대한 외래 키), 감사 여부, 실제 권한을 나타내는 정수 비트 마스크.
    • 도메인 객체를 사용할 수 있는 권한을 받은 각 수신자에 대해 단일 행이 존재합니다.

비트 마스킹

ACL 시스템은 정수 비트 마스킹을 사용합니다. 하지만 비트 시프트(bit shifting)의 세부 사항을 몰라도 ACL 시스템을 사용할 수 있습니다.

  • 기본 비트:
    • 읽기(0비트), 쓰기(1비트), 생성(2비트), 삭제(3비트), 관리(4비트).
  • 확장 가능성:
    • 기본 제공 비트를 사용하지 않고 사용자 정의 Permission 인스턴스를 구현할 수 있습니다.
    • 나머지 ACL 프레임워크는 이러한 확장과 독립적으로 동작합니다.

비트 마스킹은 도메인 객체 수와는 관계가 없습니다.

  • 32개의 비트를 사용해 권한을 정의하지만, 도메인 객체는 수십억 개가 될 수 있습니다.
  • ACL_OBJECT_IDENTITY와 ACL_ENTRY에는 도메인 객체 인스턴스마다 각각 수십억 행이 있을 수 있습니다.
  • 도메인 객체별로 비트를 할당해야 한다고 착각하는 일이 없도록 주의하세요.

주요 인터페이스

  1. Acl

    • 각 도메인 객체는 하나의 Acl 객체만 가질 수 있습니다.
    • Acl 객체는 AccessControlEntry(ACE) 객체를 내부적으로 보유하며, Acl의 소유자를 알고 있습니다.
  2. AccessControlEntry(ACE)

    • Acl은 여러 ACE 객체를 보유합니다.
    • 각 ACE는 Permission, Sid, Acl의 특정 튜플을 참조합니다.
    • ACE는 허용(granting) 또는 비허용(non-granting)일 수 있으며 감사 설정을 포함할 수 있습니다.
  3. Permission

    • 특정 불변 비트 마스크를 나타냅니다.
    • 기본 제공 권한(읽기, 쓰기 등)은 BasePermission 클래스에 정의되어 있습니다.
  4. Sid

    • 주체와 GrantedAuthority[]를 참조하는 데 사용됩니다.
    • 주요 클래스: PrincipalSid(Authentication 객체의 주체를 나타냄), GrantedAuthoritySid.
  5. ObjectIdentity

    • 각 도메인 객체는 ObjectIdentity로 표현됩니다.
    • 기본 구현: ObjectIdentityImpl.
  6. AclService

    • 주어진 ObjectIdentity에 대한 Acl을 검색합니다.
    • 기본 구현(JdbcAclService)은 LookupStrategy에 검색 작업을 위임합니다.
  7. MutableAclService

    • 수정된 Acl을 저장소에 저장할 수 있습니다.

기타 사항

  • AclService와 관련 데이터베이스 클래스는 모두 ANSI SQL을 사용합니다.
  • 주요 데이터베이스(PostgreSQL, MS SQL Server, Oracle 등)와 호환됩니다.
  • 샘플: Spring Security에는 ACL 모듈을 사용하는 Contacts Sample과 Document Management System(DMS) Sample이 포함되어 있습니다.

ACL 시스템의 이해를 돕기 위해 이 샘플들을 참고하는 것을 권장합니다.


시작하기

Spring Security의 ACL 기능을 시작하려면 ACL 정보를 저장할 위치가 필요합니다. 이를 위해 Spring에서 DataSource를 인스턴스화해야 합니다. 이 DataSource는 JdbcMutableAclServiceBasicLookupStrategy 인스턴스에 주입됩니다.

  • JdbcMutableAclService: 수정 기능을 제공합니다.
  • BasicLookupStrategy: 고성능 ACL 검색 기능을 제공합니다.

Spring Security와 함께 제공되는 샘플 중 하나를 참조하여 설정 예제를 확인하세요. 또한, 이전 섹션에서 언급된 ACL 관련 4개의 테이블을 데이터베이스에 추가해야 합니다(적절한 SQL 문은 ACL 샘플에서 확인 가능).

도메인 모델과 Spring Security ACL 패키지의 상호 운용성

필요한 스키마를 생성하고 JdbcMutableAclService를 인스턴스화한 후, 도메인 모델이 Spring Security ACL 패키지와 상호 운용 가능하도록 설정해야 합니다.

ObjectIdentityImpl은 다양한 방식으로 사용할 수 있으므로 대부분의 경우 충분히 적합합니다.

  • 도메인 객체에 public Serializable getId() 메서드가 포함되어 있다면, 추가적으로 ObjectIdentity에 대해 고민할 필요가 없을 수 있습니다.
  • 반환 타입이 long 또는 int와 같은 long 호환 타입이라면 대부분 문제없이 동작합니다.
  • ACL 모듈은 long 식별자에 의존하므로, long을 사용하지 않을 경우 여러 클래스를 재구현해야 할 수 있습니다.
  • long 타입은 모든 데이터베이스 시퀀스와 호환되고 가장 일반적인 식별자 데이터 타입이므로 Spring Security ACL 모듈에서 long 외의 식별자를 지원할 계획은 없습니다.

ACL 생성 및 수정 예제

아래 코드는 ACL을 생성하거나 기존 ACL을 수정하는 방법을 보여줍니다:

// 액세스 제어 항목(ACE)에 필요한 정보 준비
ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
Sid sid = new PrincipalSid("Samantha");
Permission p = BasePermission.ADMINISTRATION;

// 관련 ACL 생성 또는 업데이트
MutableAcl acl = null;
try {
    acl = (MutableAcl) aclService.readAclById(oi);
} catch (NotFoundException nfe) {
    acl = aclService.createAcl(oi);
}

// ACE를 통해 권한 부여
acl.insertAce(acl.getEntries().length, p, sid, true);
aclService.updateAcl(acl);

위 예제에서는 식별자 44를 가진 Foo 도메인 객체와 연결된 ACL을 가져옵니다. 그런 다음, "Samantha"라는 이름의 주체가 해당 객체를 "관리"할 수 있도록 하는 ACE를 추가합니다.

insertAce 메서드 설명

  • 첫 번째 인자: 새로운 항목을 ACL에 삽입할 위치를 결정합니다. 위 예제에서는 기존 ACE의 끝에 새로운 ACE를 추가했습니다.
  • 마지막 인자: ACE가 허용(granting)인지 거부(denying)인지 나타냅니다. 대부분의 경우 true(허용)를 사용하지만, false로 설정하면 해당 권한이 차단됩니다.

ACL과 DAO/Repository 통합

Spring Security는 DAO나 레포지토리 작업의 일환으로 ACL을 자동으로 생성, 업데이트, 삭제하는 특별한 통합 기능을 제공하지 않습니다. 대신, 위 예제와 유사한 코드를 개별 도메인 객체에 대해 작성해야 합니다.

  • 효율적인 접근법: 서비스 레이어에 AOP를 적용하여 ACL 정보를 서비스 레이어 작업과 통합하는 방법을 고려해 보세요. 이는 효과적인 방법으로 확인되었습니다.

ACL 정보의 권한 결정 로직 활용

데이터베이스에 ACL 정보를 저장한 후, 이를 실제 권한 부여 결정 로직의 일부로 사용하는 단계가 필요합니다.
여기에는 여러 가지 방법이 있습니다:
1. AccessDecisionVoter 또는 AfterInvocationProvider를 작성

  • 메서드 호출 전 또는 후에 실행됩니다.
  • 이 클래스들은 AclService를 사용해 관련 ACL을 검색한 후, Acl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)를 호출해 권한이 부여되었는지 또는 거부되었는지 판단합니다.
  1. Spring Security에서 제공하는 클래스 활용
    • AclEntryVoter
    • AclEntryAfterInvocationProvider
    • AclEntryAfterInvocationCollectionFilteringProvider
      이러한 클래스는 런타임에 ACL 정보를 평가하는 선언적 방식(declarative-based approach)을 제공합니다. 직접 코드를 작성하지 않아도 됩니다.

샘플 활용

이러한 클래스들을 사용하는 방법은 Spring Security의 샘플 애플리케이션을 참조하세요.

0개의 댓글