이 팁에서는 GlassFish v2 서블릿 컨테이너의 새로운 인증 메커니즘을 구현 및 구성하는 방법에 대해 다룹니다. GlassFish v2에는 기본, 양식, 약식 인증 등 다양한 HTTP 레이어 인증 메커니즘이 구현되어 있습니다. 이 팁에 설명된 방법에 따라 이렇게 포함된 메커니즘의 다른 구현을 추가하거나 HTTP Negotiate/SPNEGO, OpenID, CAS와 같은 새로운 메커니즘 구현을 추가할 수 있습니다.
GlassFish v2 및 JSR 196
GlassFish v2는 JRS-196, 자바 인증 서비스 제공자 컨테이너 인터페이스의 서블릿 컨테이너 프로파일을 구현합니다. JSR 196은 메시지 처리 런타임에 인증 메커니즘 구현을 통합하기 위한 표준 서비스 공급자 인터페이스(SPI)를 정의합니다. GlassFish는 JSR 196 표준을 지원하므로 이 팁에 설명된 방법을 이용하여 GlassFish에 인증 메커니즘을 추가할 수 있습니다.
JSR 196은 메시지 처리 런타임의 메시지 인증 모듈을 플러그 방식으로 구동할 수 있도록 자바 인증 및 권한 부여 서비스(JAAS)의 개념을 확대해 줍니다. 이 표준은 특정 상황에서 SPI 사용 계약의 내용을 정하기 위한 프로파일을 정의합니다. JSR-196 서블릿 프로파일은 (1) 결과 컨테이너를 새로운 인증 메커니즘으로 구현할 수 있을 것, (2) 컨테이너가 선언적 서블릿 보안 모델(보안 제약 요소를 사용하여 web.xml 파일에 선언됨)을 시행할 때 구성된 메커니즘을 적용할 것의 두 가지 조건으로 서블릿 컨테이너의 SPI 사용을 정의하고 있습니다.
JSR 196 사양은 그림 1에서 보듯이 클라이언트 측과 서버 측에 각각 2개씩 4개의 상호작용 지점으로 이루어진 간단한 메시지 처리 모델을 정의합니다.

그림 1. 보안 처리 진입 지점
메시지 처리 런타임은 이러한 상호작용 지점에서 SPI를 통해 런타임에 통합된 인증 제공자(또는 인증 모듈)에 해당 메시지 보안 처리를 위임할 때 SPI를 사용합니다.
한편 GlassFish 서블릿 컨테이너 등 이와 호환되는 서버 측 메시지 처리 런타임은 메시지 처리 모델의 validateRequest 및 secureResponse 상호작용 지점을 지원합니다. 서블릿 컨테이너는 이 상호작용 지점에서 SPI를 사용하여 해당하는 메시지 보안 처리를 서버 인증 모듈(SAM)에 위임합니다. 이 모듈은 SPI에 의해 컨테이너에 통합되어 있습니다.
GlassFish 서블릿 컨테이너 등 호환되는 서버 측 메시지 처리 런타임에 인증 메커니즘을 추가할 때는 원하는 인증 메커니즘을 구현하는 SAM을 확보하는 것이 중요합니다. 이 SAM을 직접 작성하는 것도 한 가지 방법입니다. 이제 그 방법을 알아보겠습니다.
SAM 작성
SAM은 JSR 196으로 정의하고 validateRequest 및 secureResponse 상호작용 지점에서 메시지 처리 런타임에 의해 간접적으로 호출되는 javax.security.auth.message.module.ServerAuthModule 인터페이스를 구현합니다. SAM은 ServerAuthModule 인터페이스의 다섯 가지 메소드를 반드시 구현해야 합니다. 각각에 대해 간단히 알아보겠습니다. 자세한 배경과 세부사항은 JSR 196 스펙의 "3. 서블릿 컨테이너 프로파일" 섹션을 참조하십시오.
getSupportedMessageTypes()Class개체의 어레이로 각 요소는 SAM이 지원하는 메시지 유형을 정의합니다. SAM이 서버 컨테이너 프로파일과 호환되도록 하려면 반환된 어레이에HttpServletRequest.class및HttpServletResponse.class개체가 들어 있어야 합니다.initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler 맵 옵션)
컨테이너는 SAM에 구성 값과CallbackHandler를 제공할 때 이 메소드를 호출합니다. 구성 값은 정책 인수와 옵션맵에 반환됩니다. SAM은CallbackHandler를 사용하여 암호 검증 등 컨테이너의 서비스에 액세스합니다.AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject)
컨테이너는 수신된 각HttpServletRequest를 처리할 때 이 메소드를 호출합니다. 컨테이너는 요청 및 연관된HttpServletResponse를messageInfo인수의 SAM에 전달합니다. SAM은 이 요청을 처리하며, 컨테이너에서 반환할 응답을 설정할 수 있습니다. SAM은 제공된Subject인수를 사용하여 인증 결과를 전달합니다. SAM은 컨테이너의 호출 처리를 제어하기 위해 다양한 상태 값을 반환합니다. 반환되는 상태 값과 반환 상황은 다음과 같습니다.AuthStatus.SUCCESS는 애플리케이션 요청 메시지의 검증에 성공했을 때 반환됩니다. 컨테이너는 반환된 클라이언트Subject를 사용하여 요청의 대상을 호출함으로써 이 상태 값에 응답합니다. 사용자 정의AuthConfigProvider를 사용하지 않는 경우, 이 값이 반환되면 SAM은 반드시 그CallbackHandler를 사용하여CallerPrincipalCallback을 처리해야 합니다. 이때clientSubject를 콜백 인수로 사용합니다.AuthStatus.SEND_CONTINUE는 메시지 검증이 불완전하며 SAM이messageInfo에 응답 메시지로 사용할 예비 응답을 설정했음을 나타냅니다. 컨테이너는 예비 응답을 클라이언트로 보냄으로써 이 상태 값에 응답합니다.AuthStatus.SEND_FAILURE는 메시지 검증이 실패했으며 SAM이messageInfo에 적절한 장애 응답 메시지를 설정했음을 나타냅니다. 컨테이너는 장애 응답을 클라이언트로 보냄으로써 이 상태 값에 응답합니다.AuthStatus.SEND_SUCCESS는 대개 반환되지 않습니다. 이 상태 값은 서비스 상호 작용 후, 그리고 애플리케이션 응답 처리 도중에 생성되는 다중 메시지 보안 대화 상자의 종료를 의미합니다. 컨테이너는 응답을 클라이언트로 보냄으로써 이 상태 값에 응답합니다.
또한
ValidateRequest는AuthException을 throw하여messageInfo에 장애 응답 메시지를 설정하지 않고 SAM의 메시지 처리가 실패했음을 나타내기도 합니다.secureResponse(MessageInfo messageInfo, Subject serviceSubject)
컨테이너는 애플리케이션 호출 결과 생성된 응답을 클라이언트에 보내기 전에 이 메소드를 호출합니다. 생성된 응답은messageInfo인수에 담겨 SAM으로 전달됩니다. 대개의 경우 이 메소드는SEND_SUCCESS상태만을 반환합니다.- cleanSubject(MessageInfo messageInfo, Subject subject)
이 메소드는 특정 메커니즘에만 해당되는 원칙(principal) 및/또는 자격 증명을 주제에서 제거합니다. 현재 컨테이너는 이 메소드를 호출하지 않습니다. 정상적인 구현에서는 모든 원칙이 인수 주제에서 제거될 수 있습니다.
샘플 SAM
MySam.java 클래스는 샘플로 구현된 SAM입니다. 이 샘플에서는 ServerAuthModule 인터페이스의 다섯 가지 메소드를 구현하고 있습니다. 예를 들어, initialize() 메소드 구현은 다음과 같습니다.
public void initialize(MessagePolicy reqPolicy,
MessagePolicy resPolicy,
CallbackHandler cBH, Map opts)
throws AuthException {
requestPolicy = reqPolicy;
responsePolicy = resPolicy;
handler = cBH;
options = opts;
}
validateRequest 메소드는 일부가 잘렸습니다. 권한 부여로 보호되는 리소스의 경우, validateRequest는 "FORBIDDEN" 상태 코드와 함께 "인증이 필요하며 아직 구현되지 않았습니다"라는 메시지를 표시합니다. 반면 validateRequest는 AuthStatus.SUCCESS를 반환하여 보호되지 않는 리소스에 대한 액세스를 허용합니다. SAM은 requestPolicy.isMandatory()의 반환 값을 사용하여 권한 부여로 보호되는 리소스에 대한 요청 시기를 판단합니다. 한편 서블릿 컨테이너 프로파일은 SAM이 인증 필요 시기를 판단하는 데 사용할 수 있도록 메시지 처리 런타임에서 requestPolicy를 설정할 것을 요구합니다. public AuthStatus validateRequest(
MessageInfo msgInfo, Subject client,
Subject server) throws AuthException {
try {
if (requestPolicy.isMandatory()) {
HttpServletResponse response =
(HttpServletResponse)
msgInfo.getRequestMessage();
response.setStatus
(HttpServletResponse.SC_FORBIDDEN);
response.sendError(
HttpServletResponse.SC_FORBIDDEN,
"authentication required and
not yet implemented");
return AuthStatus.SEND_FAILURE;
} else {
setAuthenticationResult(null,
client, msgInfo);
return AuthStatus.SUCCESS;
}
} catch (Exception e) {
AuthException ae = new AuthException();
ae.initCause(e);
throw ae;
}
}
샘플 SAM을 사용하려면 먼저 샘플을 컴파일, 설치, 구성해야 합니다. 그런 다음 애플리케이션에 바인딩할 수 있습니다.
SAM 컴파일 및 설치
SAM을 컴파일하려면 클래스 경로에 SPI를 넣어야 합니다. GlassFish를 설치할 때, SPI가 포함된 JAR 파일(jmac-api.jar)이 GlassFish 설치 디렉토리의 루트 아래에 있는 lib 하위 디렉토리에 설치됩니다.
SAM을 컴파일한 뒤, 컴파일된 SAM이 들어 있는 JAR 파일을 GlassFish 설치 디렉토리의 루트 아래에 있는 lib 하위 디렉토리에 복사하여 설치합니다.
SAM 구성
GlassFish v2 관리 콘솔을 사용하여 GlassFish v2용 SAM을 구성할 수 있습니다. 이 관리 콘솔의 정식 이름은 썬 자바 시스템 애플리케이션 서버 관리 콘솔입니다. 작업 단계는 다음과 같습니다.
- GlassFish V2 애플리케이션 서버가 실행 중인지 확인합니다. 실행되고 있지 않다면 다음 명령으로 서버를 시작합니다.
<GF_HOME>bin/asadmin start-domain domain1
여기서 <GF_HOME>은 GlassFish v2를 설치한 디렉토리입니다.
- 브라우저에서 GlassFish v2 관리 콘솔 URL
http://localhost:4848/을 입력하여 콘솔을 엽니다. - 사용자 ID와 암호를 입력하여 관리 콘솔에 로그인합니다.
- 왼쪽 창 아래에 있는 구성 노드를 확장합니다.
- 보안 노드를 찾아 확장한 다음 메시지 보안을 클릭합니다.
- 메시지 보안 구성 아래에 HttpServlet 레이어가 있으면 열고, 없으면 새로 만들기 버튼을 클릭하여 이 레이어를 만듭니다. 이 버튼을 클릭하면 새 메시지 보안 구성 창이 열립니다.
레이어를 만들기 위해서는 공급자를 구성해야 합니다. 다음과 같이 하십시오.
- 그림 2에 표시된 새 메시지 보안 구성 창에서 다음을 설정합니다.
공급자 유형: 서버
공급자 ID:MySam
클래스 이름: SAM(즉,tip.sam.MySam).기본 공급자: 사용 확인란을 선택하지 마십시오.
그림 2. SAM을 위한 공급자 구성
- OK 버튼을 클릭합니다. 그러면 설정이 저장되고 메시지 보안 구성 창이 열립니다.
- 인증 레이어 열에서 HttpServlet을 클릭합니다.
- 공급자 탭을 선택합니다. 그러면 공급자 구성 창이 열립니다.
- 공급자 ID 열에서 MySam을 선택합니다. 그러면 공급자 구성 편집 창이 열립니다.
- 저장 버튼을 클릭하여 공급자 구성을 마칩니다.
HttpServlet 레이어가 이미 있는 경우에는 다음과 같이 하십시오.
- 메시지 보안 구성 창에서 HttpServlet 레이어를 선택하여 엽니다.
- 공급자 탭을 선택하여 공급자 구성 창을 엽니다.
- 새로 만들기 버튼을 클릭하여 새 공급자 구성 창을 엽니다.
- 이 창의 공급자 구성 영역에서 다음을 설정합니다.
공급자 유형: 서버
공급자 ID:MySam
클래스 이름: SAM(즉,tip.sam.MySam).기본 공급자: 사용 확인란을 선택하지 마십시오.
- 그림 2에 표시된 새 메시지 보안 구성 창에서 다음을 설정합니다.
이 공급자 구성 유틸리티에는 추가 등록 정보를 구성할 수 있는 대화 상자도 있습니다. 이 대화 상자를 사용하여 구성한 추가 등록 정보는 해당 SAM의 initialize 메소드를 호출할 때 옵션 매개변수로 전달됩니다. 이 팁에서 소개하는 SAM에는 초기화 등록 정보가 필요 없으나, 예를 들어 인증 성공의 부수 효과를 더하기 위해 그룹 원칙의 이름을 지정하는 등록 정보가 지원되는 다른 SAM도 있을 수 있습니다.
이제 SAM이 설치되었으며 GlassFish v2에서 이를 사용할 수 있습니다.
SAM을 애플리케이션에 바인딩
SAM을 설치하고 구성한 뒤에는 사용자 애플리케이션 대신 컨테이너에서 사용하도록 바인딩할 수 있습니다. 애플리케이션의 패키징 및 배포를 다시 할 것인지 여부에 따라 몇 가지 옵션을 사용하여 SAM을 바인딩합니다.
옵션 1: 패키징과 배포를 다시 하려는 경우, 웹 애플리케이션의 sun-web.xml 파일에서 SAM을 바인딩할 수 있습니다. 이를 위해서는 sun-web-app 요소의 httpservlet-security-provider 속성을 구성하여 SAM 설치 시 지정된 공급자 ID(MySam)가 포함되도록 합니다.
NetBeans IDE 사용자라면 비 XML 보기를 사용하여 해당 구성 파일을 선택하고 일반 탭을 선택합니다. Http 서블릿 보안 공급자 필드에 공급자 ID를 지정합니다. 그런 다음 애플리케이션을 다시 빌드하여 배포해야 합니다.
옵션 2: 첫 번째 옵션에서는 GlassFish와 함께 제공되는 기본 AuthConfigProvider 구현을 이용했습니다. 두 번째 방법은 사용자 고유의 AuthConfigProvider를 개발한 다음 애플리케이션 대신 이를 사용하도록 Glassfish AuthConfigFactory로 등록하는 것입니다. 예를 들어, 초기화 등록 정보를 통해 SAM의 클래스 이름을 확보하는 간단한 AuthConfigProvider가 있을 수 있습니다. 이로써 해당 공급자의 등록 애플케이션을 대신하여 구성하게 됩니다. AuthConfigProvider 및 AuthConfigFactory가 제공하는 등록 장치의 기능에 대한 자세한 설명은 JRS-196 사양을 참조하십시오.
애플리케이션 대신 컨테이너에서 사용하도록 SAM을 바인딩하면 해당 애플리케이션에서 리소스를 요청할 때마다 이 컨테이너가 MySam을 호출하게 됩니다. 또한 인증 제약 조건으로 보호되는 애플리케이션 내부의 리소스를 요청하는 경우에는 MySam이 인증이 성공적으로 이루어졌는지 확인한 뒤에 그 요청을 전달합니다.
이제 validateRequest 구현을 완료하여 MySam이 인증을 수행할 수 있도록 하겠습니다.
SAM 수정
지금까지 SAM 설치, 구성 및 바인딩 단계를 마쳤습니다. 이제 인증 메커니즘의 구현으로 돌아가서 몇 가지 내용을 변경하여 validateRequest 메소드 구현을 완료하겠습니다. 설명을 간단하게 하기 위해 HTTP 기본 인증을 구현하는 SAM 또는 그와 비슷한 것으로 바꾸겠습니다.
수정된 SAM은 다음과 같습니다. 다음은 기본적인 변경 사항입니다.
- 그룹 및 암호 검증 콜백과 Base64 디코더를 사용할 수 있도록 새로 가져온 항목이 계정에 추가되었습니다.
import javax.security.auth.message.callback.GroupPrincipalCallback; import javax.security.auth.message.callback.PasswordValidationCallback; import org.apache.catalina.util.Base64; - 등록 정보 이름과 HTTP 기본 헤더 식별자를 나타내는 새 상수가 추가되었습니다.
private String realmName = null; private String defaultGroup[] = null; privte static final String REALM_PROPERTY_NAME = "realm.name"; private static final String GROUP_PROPERTY_NAME = "group.name"; private static final String BASIC = "Basic"; static final String AUTHORIZATION_HEADER = "authorization"; static final String AUTHENTICATION_HEADER = "WWW-Authenticate"; initialize메소드는group.name과realm.name등록 정보를 찾습니다.group.name등록 정보는 인증에 성공할 경우 지정되는 기본 그룹을 구성하는 데 사용됩니다.realm.name등록 정보는 www-authenticate 요청에서 브라우저로 돌려 보낼 영역 값을 정의하는 데 사용됩니다.public void initialize(MessagePolicy reqPolicy, MessagePolicy resPolicy, CallbackHandler cBH, Map opts) throws AuthException { requestPolicy = reqPolicy; responsePolicy = resPolicy; handler = cBH; options = opts; if (options != null) { realmName = (String) options.get(REALM_PROPERTY_NAME); if (options.containsKey(GROUP_PROPERTY_NAME)) { defaultGroup = new String[]{(String) options.get(GROUP_PROPERTY_NAME)}; } } }- www-authorize 헤더를 평가하고 www-authorize 요청을 발행하도록
validateRequest메소드 구현을 수정합니다.public AuthStatus validateRequest( MessageInfo msgInfo, Subject client, Subject server) throws AuthException { try { String username = processAuthorizationToken(msgInfo, client); if (username == null && requestPolicy.isMandatory()) { return sendAuthenticateChallenge(msgInfo); } setAuthenticationResult( username, client, msgInfo); return AuthStatus.SUCCESS; } catch(Exception e) { AuthException ae = new AuthException(); ae.initCause(e); throw ae; } } setAuthenticationResult메소드를 확장하여defaultGroup이 구성된 경우 이를 추가하도록 합니다.// distinguish the caller principal // and assign default groups private void setAuthenticationResult(String name, Subject s, MessageInfo m) throws IOException, UnsupportedCallbackException { handler.handle(new Callback[]{ new CallerPrincipalCallback(s, name) }); if (name != null) { // add the default group if the property is set if (defaultGroup != null) { handler.handle(new Callback[]{ new GroupPrincipalCallback(s, defaultGroup) }); } m.getMap().put(AUTH_TYPE_INFO_KEY, ""MySAM"); } }
수정된 SAM 구성
GlassFish lib 디렉토리에서 수정된 SAM을 구축 및 설치한 뒤, 브라우저에서 GlassFish v2 관리 콘솔을 사용하여 다음과 같이 SAM을 재구성해야 합니다.
- 왼쪽 창 아래에 있는 구성 노드를 확장합니다.
- 보안 노드를 찾아 확장한 다음 메시지 보안을 클릭합니다.
- 메시지 보안 구성에서 HttpServlet을 선택합니다.
- 공급자 탭을 선택합니다.
- MySam 항목을 클릭하여 공급자 구성 편집 창을 엽니다.
- 추가 등록 정보 영역에서 등록 정보 추가 버튼을 클릭하고 이름 필드에
group.name을, 값 필드에user를 입력합니다. - 등록 정보 추가 버튼을 다시 클릭하고 이름 필드에
realm.name을, 값 필드에Sam을 입력합니다. - 저장 버튼을 클릭합니다.
또는, 정의된 등록 정보 값과 다른 이름으로 두 번째 공급자를 만들 수 있습니다. 그러고 나서 애플리케이션을 새 공급자에 바인딩한 다음 애플리케이션을 다시 배포하면 됩니다.
전과 마찬가지로, 수정된 SAM을 설치하여 애플리케이션에 바인딩하면 컨테이너는 해당 애플리케이션 대신 이 SAM을 호출하게 됩니다. 애플리케이션에서 인증으로 보호된 리소스에 액세스하려고 하면 수정된 SAM이 사용자 이름과 암호를 요구합니다. SAM은 CallbackHandler를 사용하여 해당 애플리케이션에 대해 구성된 영역을 토대로 입력된 사용자 이름과 암호를 검증해 줄 것을 컨테이너에 요청합니다. 이 검증을 통과하려면 해당 애플리케이션용으로 구성된 영역에 속하는 사용자 이름과 암호를 알아야 합니다.
기본적으로, GlassFish 애플리케이션은 파일 영역을 사용하도록 구성되어 있습니다. 파일 영역에 사용자를 추가하려면 GlassFish v2 관리 콘솔을 사용하여 다음과 같이 하십시오.
- 왼쪽 창 아래에 있는 구성 노드를 확장합니다.
- 보안 노드를 찾아 확장한 다음 영역을 클릭합니다.
- 영역 아래에서 파일 영역을 엽니다.
- 사용자 관리 버튼을 클릭합니다.
- 새로 만들기를 클릭합니다.
- "사용자 ID", "새 암호" 및 "새 암호 확인"에 값을 입력합니다.
- OK 버튼을 클릭합니다.
추가 자료
다음과 같은 문서에서 JSR 196 사용에 대한 자세한 내용을 알아볼 수 있습니다.
저자 정보
Ron Monzillo는 JSR 196 및 JSR 115: 컨테이너를 위한 자바 인증 계약의 사양 설정을 이끌고 있습니다. 서블릿용의 선언적 보안 모델을 정의한 바 있으며, EJB 보안 상호운용성 프로토콜(CORBA/CSIv2)을 정의한 팀에서 팀장으로 활약했습니다. 또한 OASIS WS 보안 표준 분야에도 관여하고 있으며 WS 보안 SAML 토큰 프로파일의 주요 작성자로 참여했습니다. Ron은 자바 EE, 서블릿, GlassFish와 관련된 대부분의 보안 프로젝트에 참여하고 있습니다.
이 글의 영문 원본은
Adding Authentication Mechanisms to the GlassFish Servlet Container
에서 보실 수 있습니다.
"Java EE" 카테고리의 다른 글
- JAXR (JAVA API FOR XML REGISTRIES) (댓글 1개 / 트랙백 0개) 2005/05/18
- JAVA 트랜잭션 API 소개 (댓글 1개 / 트랙백 0개) 2005/03/23
- 환경 엔트리를 이용해서 배포의 사용자 정의하기 (댓글 2개 / 트랙백 0개) 2003/12/24
- EJB 세션 빈(Session Bean)을 모델 파사드(Model Facade)로 사용하기 (댓글 4개 / 트랙백 0개) 2007/04/10
- 레슨: JavaBeans의 개념 (댓글 0개 / 트랙백 0개) 2008/02/20
- Model Facade 사용하기 (댓글 4개 / 트랙백 0개) 2006/12/22
- 자바 기술을 이용한 AJAX의 활용 (댓글 1개 / 트랙백 0개) 2005/12/27
- JAVAMAIL API를 사용해서 HTML 이메일 보내기 (댓글 1개 / 트랙백 0개) 2004/04/28
- Java에서 RESTful 웹 서비스 구현하기 (댓글 1개 / 트랙백 0개) 2007/12/04
- 스키마 검증 프레임워크 (댓글 1개 / 트랙백 0개) 2005/11/22

댓글을 달아 주세요