제5장 Custom 보안 서비스 개발

본 장에서는 JEUS 보안 시스템의 주요 특징인 Custom 보안 서비스를 개발하는 방법에 대해 설명한다.

5.1. 개요

Custom 보안 서비스를 이용하면 JEUS 보안 시스템과 다양한 외부 보안 시스템, 보안 데이터 저장소를 쉽게 통합할 수 있다.

다음은 Custom 보안 서비스를 개발과 관련된 몇 가지 중요한 개념으로 각 절에서 상세히 설명한다.

5.2. 서비스 클래스

pluggable 보안 아키텍처에서 가장 기본이 되는 클래스는 jeus.security.base.Service 클래스이다. 서비스 클래스는 보안 서비스를 구현하려는 클래스들이 반드시 확장해야 하는 추상 클래스로 현재 jeus.security.spi 패키지에 있는 모든 SPI 클래스도 이 클래스를 확장한 것이다.

다음은 서비스 클래스의 클래스 다이어그램이다.

[그림 5.1] 서비스 클래스 다이어그램

figure service class diagram

서비스 클래스는 모든 보안 서비스에 공통적으로 적용되는 다음의 항목들을 포함하고 있다.

5.3. Custom 보안 서비스 구현 패턴

본 절에서는 Custom 보안 서비스의 구현 방법에 대해서 간략하게 설명한다.

일반적인 Custom 보안 서비스를 구현하는 과정은 다음과 같다.

  1. 사전에 보안 시스템과 보안 시스템 아키텍처를 충분히 이해하고 있어야 한다.

  2. Custom 보안 서비스가 어떤 보안 기능을 제공해야 하는지 파악한다.

  3. 해당 특성을 가지고 있는 SPI 클래스를 선택한다. 주요 SPI 클래스에 대한 자세한 내용은 “5.4. SPI 클래스”를 참고한다.

  4. 해당 SPI 클래스에 대한 문서를 주의 깊게 살펴본다(Javadoc, 참고 자료, “5.4. SPI 클래스”에 대한 설명을 참고한다).

  5. 해당 SPI 클래스의 서브 클래스를 작성하고, 서브 클래스에서 다음 메소드들을 구현한다. 그리고 반드시 파라미터가 없는 public 생성자를 제공해야 한다.

    1. 선택적으로 서비스 초기화에 사용되는 속성들을 정의한다. 각 속성은 public static final String 타입으로 각각이 무엇을 나타내는지는 문서화되어야 한다.

    2. doCreate() 메소드를 구현한다. 이 메소드는 보안 서비스가 시작할 때 단 한 번 불리며, 자원 할당과 같은 일반적인 초기화 작업을 수행한다. 이 메소드는 getProperty() 메소드를 통해 설정값을 읽어 들인다. getProperty() 메소드의 파라미터는 이전 단계에서 설정된 속성명이다.

    3. doDestroy() 메소드를 구현한다. 이 메소드는 서비스가 종료하기 전에 단 한 번 호출된다. 이 메소드는 doCreate() 메소드에서 할당된 자원을 해제하거나, 특정 파일에 로그를 남기는 등의 일반적인 clean-up 작업을 수행한다.

    4. 선택한 SPI 클래스의 모든 추상 메소드를 Javadoc에서 설명하고 있는 방식대로 구현한다.

    5. 선택적으로 JMX를 통해 서비스를 관리하는 데 사용되는 메소드들을 구현할 수 있다. getMBean() 또는 getMBeanInfo() 메소드를 구현한다.

  6. SPI를 구현한 클래스를 컴파일한다.

  7. “제2장 보안 시스템 설정”에서 설명한 대로 새로운 보안 서비스를 JEUS에 등록한다.

5.4. SPI 클래스

본 절에서는 jeus.security.spi 패키지에 포함되어 있는 다양한 SPI 클래스에 대해 설명한다.

다음은 jeus.security.spi 패키지 내에 정의된 SPI 클래스 목록이다. Cardinality는 각 도메인에 해당 SPI 클래스가 몇 개나 있을 수 있는지에 대한 설명이다. SPI 클래스에 대한 상세한 설명은 참고 자료를 참고한다.

class namePurposeCardinality

SecurityInstaller

보안 시스템을 설치하고 제거한다.

전체 JVM에 대해 오직 하나만 존재해야 한다.

1

SubjectValidationService

로그인 전에 해당 Subject의 Credential이 유효한지 아닌지 체크한다.

0개 이상

SubjectFactoryService

로그인 전에 Custom Subject를 생성한다.

1

AuthenticationService

로그인 전에 Subject를 인증한다.

1

AuthenticationRepositoryService

Subject를 Subject 저장소로부터 추가, 삭제, 조회한다.

1

IdentityAssertionService

cert-user-map.xml 파일 정보를 이용하여 Credential을 Subject에 매핑한다.

0개 이상

CredentialMappingService

Truststore 정보를 이용하여 Credential을 Subject에 매핑한다.

0개 이상

CredentialVerificationService

Subject가 가지고 있는 Credential 중 최소한 하나라도 유효한지 검증한다.

0개 이상

AuthorizationService

Subject가 특정 Role 또는 리소스에 접근할 권한이 있는지 체크한다.

1

AuthorizationRepositoryService

Policy를 저장소로부터 추가, 삭제, 조회한다.

1

EventHandlingService

보안 이벤트에 대한 Custom 이벤트 핸들러, 다양한 보안 감사를 구현한다.

0개 이상

5.4.1. SubjectValidationService SPI

jeus.security.spi.SubjectValidationService SPI는 Subject가 가지고 있는 Credential이 유효한지 아닌지를 체크하는 데 사용된다. 이것은 때때로 Credential이 유효하지 않는 경우도 있음을 나타낸다. 유효하지 않는 Credential은 곧 Subject가 유효하지 않다는 의미이고, 결과적으로 해당 Subject로는 로그인할 수 없다는 것을 나타낸다.

SubjectValidationService의 전형적인 예는 Subject가 "lock" Credential을 가지고 있는지 여부를 체크하는 것이다. 만약 "lock" Credential을 가지고 있다면, Subject는 Lock에 걸린 것으로 취급되어 더 이상의 로그인 프로세스가 진행되지 않는다.

SubjectValidationService.checkValidity(Subject) 메소드는 보통 로그인 과정에서 호출된다. 만약 이 메소드에서 SecurityException이 발생되면, 모든 로그인 과정은 실패로 돌아간다. Subject에 "lock" Credential을 자동으로 설정하기 위해서 EventHandlingService가 사용된다. 이에 관한 자세한 설명은 “5.4.10. EventHandlingService SPI”를 참고한다.

인증(파라미터로 넘어온 Subject가 실제 해당 Subject인지 검증하는 것)과 Subject 유효성 검사는 서로 별개의 것이나 로그인 과정에는 2가지 서비스가 모두 사용된다.

도메인당 0개 이상의 SubjectValidationService 인스턴스가 설정될 수 있다. 전체 SubjectValidationService에서 SecurityException이 하나도 발생하지 않으면, Subject는 유효하다고 판단되고, 로그인 과정이 계속 진행된다. 그러나 만약 SecurityException이 한 곳이라도 발생하면 전체 유효성 검사는 실패로 돌아가고 로그인 과정은 더 이상 진행되지 않는다.

참고

SubjectValidationService SPI는 CredentialVerificationService와 많은 점에서 비슷해 보이지만 기능명에서 다르다.

SubjectValidationService는 Credential을 체크해서 Subject가 유효한지 아닌지를 판단하고, CredentialVerificationService는 해당 Subject가 실제 Subject인지 증명하는 Credential을 최소한 하나라도 가지고 있는지를 체크한다. 따라서, SubjectValidationService는 Subject가 성공적으로 인증된 후 사용되고, 반면에 CredentialVerificationService는 AuthenticationService에서 인증 과정 중에 사용된다.

5.4.2. SubjectFactoryService SPI

jeus.security.spi.SubjectFactoryService SPI 클래스는 외부에서 제공하는 정보없이 Subject를 생성하는 특별한 SPI이다.

SubjectFactoryService SPI는 보통 어떤 Subject나 Credential도 파라미터로 넘겨받지 않고, Subject를 SubjectFactoryService를 통하여 생성할 때 사용된다. 예를 들어 전형적인 구현 클래스는 프롬프트로부터 사용자명과 패스워드를 입력받아 이 정보를 기초로 Subject를 생성해서 리턴한다.

SubjectFactoryService는 로그인 메커니즘에서 패스워드 이외의 Credential 타입도 지원하기 위해 만들어졌다.

5.4.3. AuthenticationService SPI

jeus.security.spi.AuthenticationService는 Subject를 인증하기 위한 클래스이다. 즉, 파라미터로 넘겨받은 Subject가 Subject 저장소에 등록되어 있는 실제 Subject와 일치하는지 검증하는 것이다.

인증 과정은 다음과 같다.

  1. jeus.security.spi.CredentialMappingService.getSubjectName(Object) 메소드를 호출하여 Subject의 Credential이 어떤 사용자에 매핑되는지 확인한다.

  2. jeus.security.spi.AuthenticationRepository.getSubject(String username) 메소드를 호출하여 Subject 저장소로부터 실제 등록되어 있는 Subject를 로컬로 복사해온다. 이를 로컬 Subject라고 한다.

  3. 만약 로컬 Subject가 null이 아니라면 로컬 Subject의 Credential과 인증할 Subject의 Credential이 일치하는지 비교한다. 이 비교는 jeus.security.spi.CredentialVerificationService.verifyCredentials(Subject, Subject) 메소드를 호출하여 수행된다.

    몇몇 경우에는 equals(Objet)를 사용하여 Credential을 비교하기도 하는데, 이는 유연한 방법이 아니다.

  4. 위의 과정이 모두 성공적으로 완료되면 로컬 Subject가 authenticate(Subject) 메소드로부터 리턴되고, 결국 이 Subject를 사용하여 SecurityCommonService로 로그인한다.

도메인당 1개 이상의 AuthenticationService를 설정할 수 있다. 만약 설정된 AuthenticationService 중 최소한 하나라도 Subject를 성공적으로 인증했다면 모든 AuthenticationService가 인증한 것으로 간주한다. 즉, 단 하나의 AuthenticationService라도 성공적으로 Subject를 인증했다면, 추가적으로 다른 AuthenticationService에 Query를 던지지 않는다.

5.4.4. AuthenticationRepositoryService SPI

jeus.security.spi.AuthenticationRepositoryService SPI는 Subject 저장소로부터 Subject를 추가, 삭제, 조회하는 메소드를 가지고 있다. 이 SPI는 AuthenticationService 를 구현하는 클래스에서 사용되거나, Jakarta EE 애플리케이션 코드 내에서 직접 사용될 수 있다.

예를 들어 웹 사이트를 통해 새로운 사용자가 등록할 때 마다, 자동으로 Subject를 Subject 저장소에 추가하는 코드를 작성하려면, 이 SPI를 사용해야 한다.

AuthenticationRepositoryService의 가장 큰 목적은 런타임에 jeus.security.base.Subject 인스턴스를 특정 저장소 타입에 저장하는 것이다. 전형적인 저장소로는 XML 파일이나 데이터베이스가 있다.

일반적으로 AuthenticationRepositoryService SPI를 별도로 구현할 필요는 없다. 그러나 디폴트 AuthenticationService를 사용하거나(사용자 인증 과정에서 AuthenticationRepositoryService.getSubject(String) 메소드를 호출하기 때문이다), Jakarta EE 애플리케이션 코드에서 직접 사용할 때는 반드시 구현해야 한다.

도메인당 단 하나의 AuthenticationRepositoryService 인스턴스만 설정될 수 있다(현재 1개 이상의 AuthenticationRepositoryService를 설정하는 것은 불가능하다).

5.4.5. IdentityAssertionService SPI

jeus.security.spi.IdentityAssertionService SPI는 Subject의 사용자명(username)이 제공되지 않는 경우에 필요하다. 즉, 메인 Principal이 null일 때이다. 이 경우에는 Subject로부터 Credential을 가져와서 cert-user-map.xml의 정보를 기반으로 Credential이 어느 사용자명에 매칭되는지 알아보게 된다. 매칭된 사용자명은 이후 인증과정에 사용된다.

전형적인 예로 java.security.Certificate 인스턴스를 들 수 있다. 이 인스턴스는 Certificate Credential을 사용하므로 메인 Principal을 가지고 있지 않다. 이 경우 인증과정 동안 Sujbect 로부터 Certificate Credential을 얻어와서 IdentityAssertionService.getIdentity(Object) 메소드에 파라미터로 넘긴다.

해당 메소드는 다시 IdentityAssertionService.doIdentity(Object)를 호출한다. 이 메소드 내부에서 cert-user-map.xml에 정의된 Certificate에 Attribute Key값에 대응하는 사용자명을 역추적하고 결국 매칭되는 사용자명을 리턴한다.

5.4.6. CredentialMappingService SPI

jeus.security.spi.CredentialMappingService는 Subject의 사용자명(username)이 제공되지 않는 경우에 필요하다. 즉, 메인 Principal이 null일 때이다. 이 경우에는 Subject로부터 Credential을 가져와서 Credential이 어느 사용자명에 매칭되는지 알아보게 된다. 매칭된 사용자명은 이후 인증 과정에 사용된다.

전형적인 예로 java.security.Certificate 인스턴스를 들 수 있다. 이 인스턴스는 Certificate Credential을 사용하므로 메인 Principal 을 가지고 있지 않다. 이 경우 인증과정 동안 Sujbect로부터 Certificate Credential을 얻어 와서, CredentialMappingService.getSubjectName(Object) 메소드에 파라미터로 넘긴다.

해당 메소드는 다시 CredentialMappingService.doGetSubjectName(Object)를 호출한다. 이 메소드 내부에서 Certificate 저장소로부터 Certificate를 통해 사용자명을 역추적하고, 결국 매칭되는 사용자명을 리턴한다.

5.4.7. CredentialVerificationService SPI

jeus.security.spi.CredentialVerificationService SPI는 새로운 타입의 Proof Credential을 지원하기 위해 사용된다.

Proof Credential는 파라미터로 넘어온 Subject가 실제 존재하는 Subject인지 검증하기 위해 사용되는 Credential이다. Proof Credential의 전형적인 예가 바로 패스워드로 jeus.security.resource.Password 클래스로 구현된다.

CredentialVerificationService SPI는 서브 클래스가 반드시 구현해야 하는 단 하나의 메소드인 doVerifyCredentials(Subject, Subject)를 선언하고 있다. 이 메소드 Signature에서 첫 번째로 나오는 Subject는 "reference Subject"라고 하고, Subject 저장소에 등록되어 있는 실제 Subject의 Credential들을 가지고 있다. 두 번째로 나오는 Subject는 "proof Subject"라고 하고, "proof Credential"들을 가지고 있다. doVerifyCredentials(Subject, Subject) 메소드는 2개의 Subject의 Credential들을 서로 비교해서 하나라도 일치하는 것이 있는지 확인한다.

Credential들 간의 매칭은 다양한 방법으로 이루어진다(보통 equals(Object) 메소드만으로는 충분하지 않다). 만약 두 Subject 간에 하나라도 일치하는 Credential이 있다면 메소드는 리턴되고, 그렇지 않다면 jeus.security.base.SecurityException이 발생한다.

참고

몇몇 경우에는 Reference Subject에 대한 정보가 필요없을 때가 있다. 즉, 특정 Proof Credential의 경우 Proof Subjet에 대한 정보만 가지고 Credential을 검증할 수 있다. 예를 들어 특정 Certificate Credential의 경우가 그렇다.

jeus.security.resource.Password를 매치하는 CredentialVerificationService는 jeus.security.impl.verification.PasswordVerificationService 클래스이다.

도메인당 0개 이상의 CredentialVerificationService를 설정할 수 있다. 만약 최소한 하나의 CredentialVerificationService에서라도 매칭이 확인되면 전체 CredentialVerificationService가 모두 성공한 것으로 간주된다. 그렇지 않으면 SecurityException이 발생하고, 전체 검증은 모두 실패한 것으로 간주되어 결과적으로 인증 과정이 실패하게 된다.

5.4.8. AuthorizationService SPI

jeus.security.spi.AuthorizationService SPI는 보안 시스템에서 권한 체크와 관련된 메소드를 정의하고 있다. 이 SPI의 목적은 "Subject S는 A라는 액션을 실행할 권한이 있는가?"라는 질문에 답하는 것이다.

전형적인 구현 클래스는 jeus.security.spi.AuthorizationRepositoryService.getPolicy(contextId) 메소드를 호출하여 그 결과로 리턴된 jeus.security.base.Policy를 분석해서 위의 질문에 대한 답을 구한다. AuthorizationRepositoryService SPI를 사용하지 않는 다른 종류의 구현도 얼마든지 가능하다.

도메인당 1개 이상의 AuthorizationService가 설정될 수 있다. 최소한 하나의 AuthorizationService에서라도 양수값을 리턴하면, 모든 권한 체크 과정을 통과한 것으로 간주한다. 즉, 하나의 AuthorizationService에서라도 Permission이 허가되었다면, 추가적으로 다른 AuthorizationService에 권한을 확인하는 Query를 던지지 않는다.

5.4.9. AuthorizationRepositoryService SPI

jeus.security.spi.AuthorizationRepositoryService SPI는 정책(Policy) 저장소로부터 jeus.security.base.Policy 인스턴스를 추가, 제거, 조회하는 메소드를 포함하고 있다. 이 SPI는 AuthorizationService 구현 클래스에서 사용되거나 Jakarta EE 애플리케이션 코드 내에서 특정 이벤트가 발생할 때 직접 정책을 저장소에 추가하거나 제거하기 위해 사용한다.

AuthorizationService의 가장 큰 목적은 런타임에 jeus.security.base.Policy를 특정 타입의 정책 저장소에 저장하는 것이다. 전형적인 구현 클래스는 XML 파일이나 데이터베이스, LDAP 서버에 정책을 저장한다.

AuthorizationRepositoryService SPI를 구현해서 사용하는 것은 선택사항이다. 그러나 디폴트 AuthorizationService 서비스를 사용한다면 이 서비스가 AuthorizationRepositoryService.getPolicy(String) 메소드를 호출하기 때문에 반드시 구현해야 한다.

도메인당 단 하나의 AuthorizationRepositoryService만 설정될 수 있다. 현재 1개 이상의 AuthorizationRepositoryService를 도메인에 설정하는 것은 불가능하다.

5.4.10. EventHandlingService SPI

EventHandlingService SPI는 각종 보안 이벤트를 캡쳐해서 처리하는 인터페이스이다.

EventHandlingService SPI를 사용하면 보안 이벤트가 발생할 때 로그를 남기고 관리자에 자동으로 메일을 보내고, Subject에 Lock을 설정하는 등의 작업을 쉽게 구현할 수 있다.

기본 개념은 매우 간단한데 보안 서비스에서 발생한 jeus.security.base.Event 타입의 이벤트는 같은 보안 도메인에 설정되어 있는 모든 EventHandlingService들에 통보된다. 그러면 EventHandlingService는 이벤트의 내용에 따라 각각 다르게 처리한다.

EventHandlingService의 예로 Subject에 Lock을 거는 경우를 생각해 볼 수 있다. AuthenticationService(인증 서비스)가 실패할 때마다 “security.authentication.failed” 타입의 이벤트가 발생한다. 그러면 lockout EventHandlingService가 이벤트를 캐치하여, handleEvent(Event) 메소드를 실행시킨다. 이 메소드 내부에서는 로그인 횟수를 카운트하고 있다가 특정 값(예를 들면 3회) 이상에 도달하면 해당 Subject에 "lock" Credential을 설정한다.

이후 SubjectValidationService는 해당 Subject에 Lock이 걸렸음을 확인하고, 결과적으로 이 Subject로의 로그인 시도는 모두 실패하게 만든다. SubjectValidationService와 EventHandlingService를 결합하면, 한번에 3회 이상 로그인에 실패한 Subject에 Lock을 걸어 더 이상 시스템에 로그인 시도를 할 수 없게 만드는 기능을 추가할 수 있다.

그러나 EventHandlingService SPI가 가장 빈번하게 사용되는 곳은 이벤트를 기록하는 Logger를 구현할 때 이다. 이벤트는 일반 텍스트나 XML 파일에 기록되기도 하지만, 때때로 부인방지를 위해 암호화된 파일이 사용되기도 한다.

도메인당 0개 이상의 EventHandlingService를 설정할 수 있다.

5.4.11. Dependencies between SPI Implementations

SPI 클래스 간에는 의존이 존재할 수 있다. 예를 들어 AuthenticationService는 AuthenticationRepositoryService에 의존한다.

참고

의존은 하나의 SPI 구현 클래스가 다른 SPI 구현 클래스의 static 메소드를 호출한다는 것을 말한다. AuthenticationService 구현 클래스를 AuthenticationServiceImpl이라 하면 이 클래스는 AuthenticationRepositoryService.getSubject()를 호출하여 Subject에 대한 정보를 얻어온다.

SPI 클래스 간에는 의존이 존재할 수 있는 것은 의존이 존재하지 않을 수도 있다는 것을 의미한다. 이는 SPI 클래스를 어떻게 구현하느냐에 결정된다. 어떤 SPI 클래스도 결코 직접적으로 다른 SPI 클래스의 메소드를 호출하지 않는다 (몇 가지 사소한 예외를 제외함). 일반적으로 SPI 를 구현한 클래스에서 다른 SPI 구현 클래스의 메소드를 호출함으로써 의존이 성립된다.

다음 그림은 디폴트 보안 시스템 구현에서 SPI 구현 클래스 간의 의존에 대한 설명으로 클래스 간의 관계를 단순화시킨 것이다.

[그림 5.3] 기본 보안 시스템 구현에서의 SPI 구현 클래스

figure base security system spi implement class

5.5. 보안 서비스 설정

새로운 SPI 구현 클래스를 컴파일한 다음, 이 클래스를 JEUS 보안 시스템에 등록해야 한다. 등록하는 방법은 “2.3. 보안 도메인 구성요소 설정”을 참고한다.