내용 목차
본 장에서는 JACC의 목적을 설명하고 Custom JACC Provider를 구현하여 JEUS 보안 시스템에서 사용하는 방법에 대해 간단히 설명한다.
JACC(Java Authorization Contract for Containers)는 J2EE 1.4에서 처음으로 소개되었고, 현재 버전은 Jakarta™ Authorization 1.5이다.
Jakarta™ Authorization 배경에는 다음의 2가지 기본 목적이 있다.
Jakarta™ Enterprise Beans와 서블릿의 권한을 체크하는 경우 표준 SPI를 제공한다.
기존 Java SE 보안 모델과 Jakarta™ EE 보안 모델 간의 조화를 추구한다.
Jakarta™ Authorization 는 Jakarta™ Enterprise Beans와 서블릿의 접근 권한을 정의하고 처리하는 표준적인 방법을 제시하고 있다. 따라서, JACC Provider를 작성하면 Jakarta™ Authorization 호환 Jakarta™ EE 서버에서 사용할 수 있다.
전체 Jakarta™ Authorization 스펙은 다음과 같이 3개의 세부 규약으로 구성되어 있다.
Provider 설정 규약
Policy 설정 규약
Policy 결정 및 집행 규약
JACC 1.5 스펙(JSR-115)에 대한 자세한 정보는 https://jakarta.ee/specifications/authorization/1.5/의 스펙 문서를 참고한다.
JACC Provider 설정 규약은 애플리케이션 서버의 런타임 환경에 JACC Provider를 통합하는 방법을 기술하고 있다. 즉, 이 스펙은 JACC Provider를 Jakarta™ EE 서버에 등록하는 방법을 정의하고 있다. 일반적으로 이 과정은 매우 단순하다.
javax.security.jacc.policy.provider 시스템 속성에 Custom java.security.Policy 클래스 이름을 설정한다. JACC Provider는 Custom java.security.Policy를 포함하고 있다.
Jakarta™ EE 서버는 이 클래스명을 읽어서 인스턴스를 만든 다음 java.security.Policy 타입으로 캐스팅한다.
위의 과정이 성공적으로 완료되었다면, java.security.Policy.setPolicy() 메소드를 호출하여 디폴트 Java SE Policy 클래스를 새로 생성한 JACC Policy 클래스로 교체한다.
모든 권한 체크는 새로 등록된 JACC Policy를 통해서 이루어지고 java.security.Policy.getPolicy() 메소드를 통해 현재 JACC Policy를 얻어올 수 있다.
JACC Provider 설정 규약에 대한 자세한 설명은 JACC 스펙을 참고한다.
Policy 설정 규약은 Jakarta™ EE DD 파일(ejb-jar.xml, web.xml)에 설정된 보안 제약(security constraints)을 javax.security.jacc 패키지에 정의된 java.security.Permission set으로 매핑하는 방법과 이러한 Permission을 JACC Provider(Custom java.security.Policy)에 추가하는 방법을 기술하고 있다.
이는 결국 Jakarta™ Enterprise Beans와 서블릿에서 JACC Provider가 권한 부여와 관련된 결정을 내릴 수 있게 만든다.
Policy 설정 규약은 Principal-to-Role 매핑에 대한 언급이 없다. 오로지 Jakarta™ EE DD 파일에 근거하여 Role-to-Resource 매핑을 어떻게 할지만 정의하고 있다. 따라서 Princiapl-to-Role 매핑은 JACC Provider를 공급하는 업체에 따라 각각 다른 방법으로 정의되어 있다.
Policy 설정 과정을 간단하게 설명하면 다음과 같이 구성되어 있다.
시스템 속성(-D 속성으로 설정) javax.security.jacc.PolicyConfigurationFactory.provider에 Custom javax.security.jacc.PolicyConfigurationFactory 클래스명을 설정한다.
JACC Provider는 Custom javax.security.jacc.PolicyConfigurationFactory를 포함하고 있다.
Jakarta™ EE 서버는 이 속성값을 읽어서 해당 클래스의 인스턴스(PCF)를 생성한다.
서블릿과 Jakarta™ Enterprise Beans 모듈을 deploy한다.
Deployment 코드는 web.xml과 ejb-jar.xml DD 파일을 읽어들여 설정된 security constraints 항목을 JACC Permission 인스턴스들로 전환한다. 이 Permission 클래스는 javax.security.jacc 패키지에 포함되어 있다. 이때 각 Permission 인스턴스는 서블릿과 Jakarta™ Enterprise Beans에 설정되어 있는 Role-to-Resource 매핑을 나타낸다.
Deployment 코드는 PCF.getPolicyConfiguration() 메소드를 호출하여 javax.security.jacc.PolicyConfiguration 타입의 인스턴스(PC)를 리턴받는다.
Deployment 코드는 4번 과정에서 생성된 Role-to-Resource Permission들을 PC의 다양한 메소드를 사용하여 PC에 추가한다.
모든 Permission을 PC에 추가하고 나면, PC의 commit() 메소드를 호출한다.
서블릿과 Jakarta™ Enterprise Beans 모듈이 undeploy될 때 PC.delete() 메소드가 호출된다. 이는 해당 서블릿 모듈과 Jakarta™ Enterprise Beans 모듈에 대한 Permission을 제거한다.
Policy 설정 과정에 대한 자세한 설명은 JACC 스펙을 참고한다.
Policy 결정 및 집행 규약은 이름에서도 알수 있듯이 Jakarta™ Enterprise Beans와 서블릿에서 접근 권한과 관련된 결정을 어떻게 내리고 집행할 것인지 기술하고 있다. 이는 앞서 설명한 2가지 세부 규약이 모두 충족된 이후에 적용된다.
Policy 결정 및 집행은 다음과 같은 과정으로 진행된다. 본 절에서는 서블릿을 예로 들어 설명하지만, Jakarta™ Enterprise Beans의 경우에도 아래의 설명과 동일하다.
해당 서블릿 페이지에 대한 요청이 들어온다.
서블릿 컨테이너는 javax.security.jacc.PolicyContext 클래스를 사용하여 JACC Context 정보를 설정한다.
서블릿 컨테이너는 2가지 JACC Web Permission(javax.security.jacc 패키지에 정의되어 있다)을 생성한다. 이 Permission 인스턴스는 현재 요청한 서블릿에 설정된 Permission을 나타낸다.
서블릿 컨테이너는 서블릿 요청자가 위의 2가지 Permission을 가졌는지 알아보기 위해 JACC Provider에 Query를 던진다. Query를 해석하는 데는 여러 가지 방법이 있다.
예를 들어 Policy.implies() 메소드로 확인할 수 있다. 이때 파라미터로 요청자의 Principal로 초기화된 java.security.ProtectionDomain에서 체크하는 모든 Permission(들)이 사용된다.
JACC Provider는 2번 과정에 설정된 context 정보, 3번에서 생성한 Permission 인스턴스, 요청자의 Principal(s), Principal-to-Role 매핑, Role-to-Resource 매핑 등을 모두 사용하여, 현재 요청자가 서블릿 페이지에 접근 권한을 가졌는지를 판단한다.
권한 체크 결과가 양수값이라면 서블릿 컨테이너는 요청자가 서블릿 페이지에 접근하도록 허락한다. 그렇지 않다면, 권한 체크에 실패했다는 에러 페이지가 나타난다.
Policy 결정 및 집행 규약에 대한 자세한 설명은 JACC 스펙을 참고한다.
본 절에서는 Custom JACC Provider를 개발하는 방법과 JACC Provider 개발과 관련된 몇 가지 지침을 설명한다.
다음은 Custom JACC Provider를 구현하기 위해 필요한 클래스들과 그들 간의 관계를 단순하게 표현한 클래스 다이어그램이다. 이 다이어그램에서는 클래스 이름을 "MYJACC"로 시작했지만, 어떤 이름이 와도 상관없다.
JACC Provider를 구현할 때 필요한 클래스는 다음과 같다.
JACC Provider의 핵심은 java.security.Policy의 서브 클래스를 작성하는 것이다. 이 클래스는 JACC에서 실제 권한을 체크할 때 사용된다.
java.security.Policy의 서브 클래스를 작성하기 위해 java.security.Policy를 상속하는 새로운 클래스를 정의한다(이를 MyJACCPolicy라고 하자). 그리고 나서 “6.2.3. Policy 결정 및 집행 규약”에서 설명한 Policy 결정 및 집행 규약을 구현하기 위해 implies() 메소드를 재정의한다. 구현할 때 권한 체크 질의를 해석하기 위해 PolicyConfiguration 인터페이스를 통해 추가된 Permission과 Principal-to-Role 매핑을 고려해야 한다.
MyJACCPolicy는 파라미터가 없는 public 생성자를 제공해서 Jakarta™ EE 서버가 인스턴스를 쉽게 생성할 수 있게 해야 한다.
javax.security.jacc.PolicyConfigurationFactory
Policy 설정 규약을 구현하기 위해 애플리케이션 서버는 Permission 인스턴스를 JACC java.security.Policy에 추가할 수 있어야 한다. 이 작업은 javax.security.jacc.PolicyConfiguration 인터페이스를 통해 이루어진다. PolicyConfiguration 인스턴스를 생성하기 위해 추상 클래스인 javax.security.jacc.PolicyConfigurationFactory가 필요하다.
PolicyConfigurationFactory의 서브 클래스를 작성하기 위해 javax.security.jacc.PolicyConfigurationFactory를 상속하는 새로운 클래스를 정의한다(이를 MyJACCPolicyConfigurationFactory라 하자). 이 클래스는 getPolicyConfiguration()를 반드시 재정의해야 한다.
MyJACCPolicyConfigurationFactory 클래스는 파라미터가 없는 public 생성자를 제공해서 Jakarta™ EE 서버가 해당 클래스의 인스턴스를 쉽게 생성할 수 있도록 해야 한다.
javax.security.jacc.PolicyConfiguration
Policy 설정 규약을 구현하기 위해 애플리케이션 서버는 Permission 인스턴스를 JACC java.security.Policy에 추가할 수 있어야 한다. 이 작업은 javax.security.jacc.PolicyConfiguration 인터페이스를 통해 이루어진다. 이 인터페이스의 인스턴스는 위에서 설명한 대로 javax.security.jacc.PolicyConfigurationFactory를 통해 생성된다.
JACC Provider는 javax.security.jacc.PolicyConfiguration을 구현한 클래스를 반드시 포함하고 있어야 한다(이 구현 클래스를 MyJACCPolicyConfiguration라고 하자).
Custom javax.security.jacc.PolicyConfigurationFactory 클래스인 MyJACCPolicyConfigurationFactory는 항상 MyJACCPolicyConfiguration 인스턴스를 리턴해야 한다.
본 절에서는 각 클래스 구현 방법에 대해 대략적으로 설명했다. 구현하는 방법에 대한 자세한 설명은 JACC 스펙의 Policy 결정 및 집행 규약과 각 클래스에 대한 Jakarta™ EE Javadoc을 확인한다.
일반적으로 JACC Provider와 Provider가 참조하는 클래스를 JAR로 묶어, 해당 애플리케이션 서버에 위치시킨다(이를 "MYJACCProvider.jar"라고 하자).
애플리케이션 서버의 Path에 JACC Provider JAR 파일의 Path를 포함시키고 필요한 경우에는 특정 시스템 속성값을 설정한다. 이 과정은 애플리케이션 서버마다 조금씩 달라진다. JEUS에서의 설정 방법은 “6.4. JEUS 보안 시스템과 JACC Provider 통합”에서 설명한다.
JEUS 보안 시스템은 매우 간단한 디폴트 JACC Provider를 제공하고 있다. 일반적으로 이 디폴트 Provider를 JACC 스펙을 테스트 이외의 용도로 사용하는 것을 권장하지 않는다. 대신 이전 장에서 설명한 Default Authorization Provider를 사용할 것을 권장한다.
그러나 디폴트 이외의 JACC Provider를 사용하려면 상용 제품은 제공되고 있지 않으므로 자신만의 JACC JAR Archive를 직접 만들고 Archive 내에 JACC Provider 파일을 포함시키도록 한다.
본 절에서는 JEUS 보안 시스템과 디폴트 JACC Provider를 통합하는 방법에 대해 설명한다.
JEUS 보안 시스템과 JACC Provider를 통합하기 위한 과정은 다음과 같다.
Principal-to-Role 매퍼를 구현한다.
JACC 인터페이스는 Principal-to-Role 매핑에 대해 어떤 것도 언급하고 있지 않고 단지 Role-to-Resource 매핑만 정의하고 있다. Principal-to-Role 매핑을 위해 JEUS 만의 별도의 인터페이스가 필요하다. 이를 위해 JEUS는 jeus.security.impl.aznrep.JACCPrincipalRoleMapper라는 인터페이스를 제공한다. 이 인터페이스는 구현해야 할 단 하나의 메소드인 addPrincipalRoleMapping(PermissionMap map, String policyId)을 포함하고 있다. 이 메소드는 Principal-to-Role 매핑을 policyId로 대표되는 PolicyConfiguration에 추가한다.
Principal-to-Role 매핑은 Jakarta™ EE에서 애플리케이션 범위를 가진다는 점에 주의한다. 동일 Jakarta™ EE 애플리케이션에 속한 모든 Principal-to-Role 매핑은 하나의 Map에 병합되기 때문이다. PermissionMap 클래스와 PermissionMap 인스턴스를 서로 병합하는데 사용되는 add() 메소드에 대한 자세한 정보는 PermissionMap 클래스에 대한 API 문서를 참고한다.
JACCPrincipalRoleMapper 인터페이스를 구현한 클래스는 파라미터가 없는 public 생성자를 제공해야 한다. 그리고 반드시 JACC Provider JAR 내에 추가되어야 한다. JACCPrincipalRoleMapper 인터페이스에 대한 자세한 정보는 참고 자료와 Javadoc을 확인한다.
JACCPrincipalRoleMapper가 Pricinpal-to-Role 매핑을 생성하는 과정은 다음과 같다.
jeus.security.jacc.principalRoleMapper 시스템 속성으로 jeus.security.jacc.principalRoleMapper를 구현한 클래스명을 설정한다.
설정이 완료되면 jeus.security.impl.aznrep.JACCAuthorizationRepositoryService를 구현한 클래스가 이 속성값을 읽어 Class.forName(mapperClassname).newInstance() 메소드로 해당 인스턴스를 생성한다.
생성된 인스턴스의 addPrincipalRoleMapping() 메소드를 호출하여 JEUS DD 파일에 명시된 Principal-to-Role 매핑을 생성해서 추가한다.
보안 설정 파일을 설정한다.
JEUS 보안 시스템은 2개의 어댑터 클래스를 사용하여 JEUS native authorization API와 JACC authorization API를 연결한다.
2개의 어댑터 클래스와 각각의 역할은 다음과 같다.
jeus.security.impl.azn.JACCAuthorizationService
컨테이너에서 Policy 결정 및 집행 규약을 구현하는 부분으로 권한을 체크하기 위해 java.security.Policy.getPolicy().implies() 메소드를 호출한다.
jeus.security.impl.aznrep.JACCAuthorizationRepositoryService
Policy 설정 규약을 구현한 부분으로 컨테이너가 생성한 jeus.security.base.Policy 인스턴스를 JACC Provider의 구성 요소인 PolicyConfiguration 인스턴스에 추가하는 역할을 한다.
JACC를 작동시키기 위해서는 이 2가지 보안 서비스가 domain.xml의 도메인 서비스 정의에 설정되어 있어야 한다.
[예 6.1] JACC 보안 설정 파일 설정 : <<domain.xml>>
<?xml version="1.0"?> ... <security-domains> <default-application-domain>JACC_DOMAIN</default-application-domain> ... <security-domain> <name>JACC_DOMAIN</name> <authorization> <jacc-service/> </authorization> </security-domain> . . . </security-domains>
시스템 Path에 JACC Provider JAR Path를 추가한다.
시스템 Path에 JACC Provider JAR 파일을 추가하기 위해 다음의 디렉터리에 JAR 파일을 위치시킨다.
JEUS_HOME/lib/system
Java 시스템 속성을 설정한다.
JACC 규약과 JEUS는 애플리케이션 서버가 JACC Provider를 인식하도록 다음 3가지 Java 시스템 속성을 규정하고 있다.
javax.security.jacc.policy.provider
JACC Provider를 나타내며, java.security.Policy를 구현한 클래스명이다.
javax.security.jacc.PolicyConfigurationFactory.provider
PolicyConfiguration 인스턴스를 생성하고 로딩하는 PolicyConfigurationFactory를 구현한 클래스명이다.
jeus.security.jacc.principalRoleMapper
jeus.security.impl.aznrep.JACCPrincipalRoleMapper 인터페이스를 구현한 클래스명으로 JEUS DD 파일로부터 Principal-to-Role 매핑을 생성한다.
위의 3가지 시스템 속성은 모든 서버에 대해서 domain.xml에 <jvm-option>으로 설정되어야 한다.
[예 6.2] JACC에 대한 Java 시스템 속성 설정 : <<domain.xml>>
<?xml version="1.0"?>
<domain xmlns=“http://www.tmaxsoft.com/xml/ns/jeus”>
<servers>
<server>
<name>server1</name>
<!-- server JVM option -->
<jvm-config>
. . .
<jvm-option>
-Djavax.security.jacc.policy.provider=
myprovider.MyJACCPolicy
</jvm-option>
<jvm-option>
-Djavax.security.jacc.PolicyConfigurationFactory.provider=
myprovider.MyJACCPolicyConfigurationFactory
</jvm-option>
<jvm-option>
-Djeus.security.jacc.principalRoleMapper=
myprovider.MyJACCPrincipalToRoleMapper
</jvm-option>
</jvm-config>
. . .
이전에 언급한 대로 디폴트 JACC provider 클래스명은 다음과 같다.
구분 | 클래스명 |
---|---|
Policy | jeus.security.impl.jacc.JACCPolicyWrapper |
PolicyConfigurationFactory | jeus.security.impl.jacc.JACCPolicyConfigurationFactoryImpl |
JACCPrincipalRoleMapper | jeus.security.impl.jacc.JACCDefaultPrincipalRoleMapper |
이 클래스들은 모두 다음의 위치에 패키징되어 있다.
JEUS_HOME/lib/system/jeus.jar