제27장 JAX-RPC 웹 서비스의 보안

내용 목차

27.1. 개요
27.2. 전송 수준 보안
27.3. 메시지 수준 보안
27.3.1. 웹 서비스 보안 적용
27.3.2. 웹 서비스 보안 아키텍처
27.3.3. JEUS 웹 서비스 보안 설정
27.3.4. 패스워드 Callback 클래스 생성
27.3.5. JEUS 웹 서비스 서버 보안 적용 예제
27.3.6. JEUS 웹 서비스 클라이언트 보안 적용 예제
27.3.7. 보안 API를 이용한 JEUS 웹 서비스 클라이언트의 작성
27.4. 접근 제어 설정
27.4.1. Java 클래스 웹 서비스 접근 제어 설정
27.4.2. EJB 웹 서비스의 접근 제어 설정
27.4.3. 접근 제어가 설정된 웹 서비스 호출

본 장에서는 JEUS에서 JAX-RPC 웹 서비스에 보안을 적용하기 위한 방법을 설명한다.

27.1. 개요

전통적으로 웹 서비스에 보안을 적용하기 위해서는 다음과 같은 2가지 방식이 존재한다.

  • 전송 수준 보안(Transport-level Security)

    SSL을 이용하여 클라이언트와 웹 서비스 사이의 연결의 보안을 보장한다.

    전송 수준 보안을 적용하려면 클라이언트와 JEUS 서버 간의 연결을 SSL을 이용하여 안전하게 할 수 있다. 그러나 이러한 방식은 연결 자체만을 안전하게 하며 클라이언트와 JEUS 서버 사이에 라우터나 메시지 큐와 같은 매개체(Intermediary)가 있다면 매개체는 SOAP 메시지를 암호화되지 않은 읽히기 쉬운 텍스트 문서 형태로 가질 수 있다. 또 전송 수준 보안은 전체적인 메시지를 다루므로 메시지 일부에의 보안 적용이 불가능하다.

  • 메시지 수준 보안

    SOAP 메시지를 전자 서명이나 암호화한다.

    메시지 수준 보안은 SSL의 보안상의 장점을 포함하면서 부가적인 유연성을 제공한다. 메시지 수준 보안은 메시지 전달 과정에서 하나 이상의 매개체가 존재하더라도 보안이 유지되는 End-to-End 보안이며, 연결 자체의 보안 유지된다는 SOAP 메시지 자체의 서명과 암호화를 의미한다. 또한 부분적인 서명과 암호화가 가능하다는 장점이 있다.

27.2. 전송 수준 보안

웹 서비스의 전송 수준 보안은 웹 서비스 클라이언트 응용 프로그램과 웹 서비스 간의 연결을 SSL을 이용하여 안전하게 하는 것을 의미한다.

전체적인 절차는 다음과 같다.

  1. JEUS 서버의 SSL 설정한다.

    웹 서비스 개발 부분은 추가 작업이 필요하지 않다. JEUS 서버에서의 SSL 설정에 대한 자세한 내용은 "JEUS Web Engine 안내서"를 참고한다.

  2. 다음의 과정으로 클라이언트 응용 프로그램의 SSL을 설정한다.

    1. 인증서를 가져온다(Internet Explorer 등을 통해 인증서를 로컬 디렉터리에 저장한다).

    2. 가져온 인증서를 Keystore에 저장한다.

    3. wsdl2java를 이용하여 WSDL로부터 Stub을 생성하거나 클라이언트에서 웹 서비스를 호출하기 위해 클라이언트를 실행하려고 할 때 시스템 프로퍼티 값을 다음과 같이 설정한다.

      –Djavax.net.ssl.trustStore=keystore_name
      –Djavax.net.ssl.trustStorePassword=keystore_password
    4. Ant가 아닌 wsdl2java 콘솔 툴을 사용한다면 다음과 같은 환경변수 설정이 필요하다.

      set WSDL2JAVA_OPTS=-Djavax.net.ssl.trustStore=keystore_name 
            -Djavax.net.ssl.trustStorePassword=keystore_password

27.3. 메시지 수준 보안

메시지 수준의 보안은 웹 서비스와 웹 서비스를 호출하는 클라이언트 간의 SOAP 메시지가 서명이나 암호화되는 것을 의미한다.

JEUS 웹 서비스는 다음과 같은 OASIS 웹 서비스 보안 표준 1.0을 지원한다.

  • OASIS Stardard 1.0

    • SOAP Message Security V1.0

    • Username Token Profile V1.0

    • X.509 Token Profile V1.0

위와 같은 표준들은 인증, 권한 부여, 데이터 무결성 및 기밀성 보장에 관한 요구 조건을 명시하며 각종 기업 애플리케이션들에 존재하는 보안 쟁점들을 해결할 수 있게 한다.

27.3.1. 웹 서비스 보안 적용

JEUS 웹 서비스에서는 다음과 같이 적용이 가능하다.

  • 클라이언트는 X.509 인증서를 사용하여 SOAP 메시지의 암호화와 서명을 하여 보안이 적용된 웹 서비스를 호출할 수 있고, 웹 서비스는 다시 X.509 인증서를 이용하여 SOAP 메시지를 암호화와 서명을 하여 응답으로 내보낼 수 있다.

    SOAP 메시지는 메시지 안에 모든 보안 관련 정보들을 포함하고 있으므로, 클라이언트와 웹 서비스 사이의 매개체들은 SOAP 메시지에서 필요한 부분만 골라서 처리하게 할 수 있다.

  • 기본적으로 JEUS 웹 서비스는 암호화하는 부분을 별도로 지정하지 않을 경우 SOAP 메시지의 Body 부분을 암호화하지만 어떤 부분이든지 암호화할 수 있다. 서명도 마찬가지이며, 이 2가지는 서로 혼용될 수 있다.

27.3.2. 웹 서비스 보안 아키텍처

다음은 JEUS 웹 서비스 보안 아키텍처를 나타내는 그림이다.

[그림 27.1] JEUS 웹 서비스 보안 아키텍처

JEUS 웹 서비스 보안 아키텍처

Server Keystore 설정은 jeus-webservices-dd.xml에 설정되고, Client Keystore 설정은 jeus-web-dd.xml에 설정된다.

JEUS Java EE 웹 서비스 클라이언트와 웹 서비스 간의 보안 메시지의 송수신

웹 서비스에 보안을 적용하는 경우 jeus-webservices-dd.xml에 <security>를 설정하고 Keystore를 생성해야 한다. 그리고 Keystore에 접근하기 위한 암호나 사용자명 토큰(UsernameToken)에 사용할 암호를 설정하기 위해서 Callback 클래스를 하나 생성해야 한다. 이 애플리케이션을 JEUS 서버에 재배치함으로써 보안 설정이 적용된다.

Java EE 클라이언트는 jeus-web-dd.xml에 <security>를 설정해야 하며, 클라이언트의 키를 저장할 Keystore를 생성해야 한다. 사용자명 토큰(UsernameToken)에 사용할 암호와 Keystore의 개인 키를 가져오기 위한 암호를 설정하기 위해서 Callback 클래스를 하나 생성해야 한다.

Java EE 클라이언트 애플리케이션이 실행되면 웹 서비스 클라이언트 런타임은 jeus-web-dd.xml에서 <security>의 설정된 값에 따라 보안 로직을 동작시켜 클라이언트 Keystore에서 개인 키를 꺼내어 서명을 하거나 서버의 공개 키를 가지고 메시지를 암호화하여 JEUS 서버의 웹 서비스 서버 런타임으로 메시지를 보낸다.

서버 런타임은 jeus-webservices-dd.xml에서 <security>에 설정된 값을 바탕으로 보안 로직을 실행시킨다.

  • 메시지가 서명이 되어 있을 경우 메시지에 포함된 서명에 사용된 키에 대한 정보를 바탕으로 클라이언트의 공개 키를 서버의 Keystore에서 인출하여 서명을 검증한다.

  • 메시지가 암호화되어 있을 경우 메시지에 포함된 암호화에 사용된 키 정보를 바탕으로 서버의 Keystore에서 서버의 개인 키를 인출하여 암호를 해독한다.

서버에서 웹 서비스 호출이 끝나고 응답을 돌려 보내는 경우 보안을 적용할 때는 위의 과정과 유사하게 진행된다.

  • 웹 서비스는 jeus-webservices-dd.xml에 <security>에 설정된 값을 바탕으로 Keystore에서 서버의 개인 키나 클라이언트의 공개 키를 꺼내어서 응답 메시지의 일부 또는 전체를 서명하거나 암호화한다.

  • 클라이언트 런타임은 jeus-web-dd.xml의 <security>에 설정된 값을 바탕으로 클라이언트 Keystore에서 서버의 공개 키나 클라이언트의 개인 키를 꺼내어 이를 사용하여 응답 메시지를 검증하고 해독한다.

JEUS와 타 벤더 간 보안 메시지의 상호 호환 및 운영성

웹 서비스의 보안은 메시지 수준의 보안을 적용하고 사용하게 되어 있다. 보안이 적용된 SOAP 메시지 내부에는 보안에 관련된 모든 정보가 포함되어 있으며 이러한 정보들이 OASIS의 웹 서비스 보안 표준을 만족하게 될 경우, 여러 벤더 간의 상호 운영성이 보장된다.

JEUS는 OASIS의 보안 표준의 최신 버전인 웹 서비스 보안 표준 1.0을 준수하고 있으며, 따라서 이 표준을 준수하는 벤더 간의 메시지 송수신 및 처리를 할 수 있다.

27.3.3. JEUS 웹 서비스 보안 설정

JEUS 웹 서비스와 클라이언트에서 보안 메시지를 송수신하기 위해서는 JEUS 웹 서비스는 jeus-webservices-dd.xml에 <security>를 추가 설정하고, 클라이언트는 jeus-web-dd.xml에 <security>를 추가 설정해야 한다.

서버 설정

jeus-webservices-dd.xml에 추가되는 <security>는 웹 서비스의 포트 하나당 하나의 설정이 요구된다. 보다 자세한 설명은 "JEUS XML Reference"의 "23. jeus-webservices-dd.xml"을 참고한다.

다음은 서버 설정에 대한 예이다.

[예 27.1] <<jeus-webservices-dd.xml>>

<jeus-webservices-dd>
    <service>
        <webservice-description-name/>
        <port>
            <port-component-name/>
            <security>
                <request-receiver/> 
                <response-sender/>
            </security>
        </port>
    </service>
</jeus-webservices-dd>


<security>의 하위 element인 <request-receiver>에 서버가 클라이언트로부터 받는 메시지의 보안 처리를 위해 필요한 정보를 설정해야 한다. <response-sender>에는 서버가 클라이언트로 보내는 메시지의 보안 처리를 위해 필요한 정보를 설정해야 한다.

클라이언트 설정

jeus-web-dd.xml에 추가되는 <security>는 각각의 <port-info>마다 하나의 설정이 필요하다. 보다 자세한 설명은 "JEUS XML Reference"의 "19. jeus-web-dd.xml"을 참고한다.

다음은 클라이언트 설정을 구현한 예이다.

[예 27.2] <<jeus-web-dd.xml>>

<jeus-web-dd>
    <service-ref>
        <service-client>
            <service-ref-name/>
            <port-info>
                <wsdl-port/>
                <security>
                    <request-sender/>
                    <response-receiver/>
                </security>
            </port-info>
            . . .
        </service-client>
        <service-client>
            . . .
        </service-client>
        . . .
    </service-ref>
</jeus-webservices-client-dd>


<security>의 하위 element인 <request-sender>에는 클라이언트가 서버로 보내는 메시지의 보안 처리를 위해 필요한 정보를 설정해야 한다. <response-receiver>에는 클라이언트가 서버로부터 받는 메시지의 보안 처리를 위해 필요한 정보를 설정해야 한다.

27.3.4. 패스워드 Callback 클래스 생성

JEUS 웹 서비스에 보안을 적용하기 위해서는 패스워드 Callback 클래스의 생성이 필요하다. 이는 Keystore에 저장된 개인 키의 암호를 설정하거나 사용자명 토큰에 사용될 암호를 설정하는 등의 작업 때문에 필요하다. Callback 클래스는 javax.security.auth.callback.CallbackHandler를 구현한 클래스여야 한다.

다음은 패스워드 Callback 클래스를 구현한 예이다.

[예 27.3] << PWCallback.java >>

package jeustest.webservices.wssec.doall;

import com.tmax.ws.security.WSPasswordCallback;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;

public class PWCallback implements CallbackHandler {
    public void handle(Callback[] callbacks)
        throws IOException, UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
            if (callbacks[i] instanceof WSPasswordCallback) {
                WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];
                checkPassword(pc);
            } else {
                throw new UnsupportedCallbackException(
                    callbacks[i], "Unrecognized Callback");
            }
        }
    }

    public void checkPassword(WSPasswordCallback pc) {
        String userId = pc.getIdentifier();
        if (pc.getUsage() == WSPasswordCallback.USERNAME_TOKEN) {
            if (userId.equals("16c73ab6-b892-458f-abf5-2f875f74882e")) {
                pc.setPassword("security2");
            }
            if (userId.equals("key_tmax1")) {
                pc.setPassword("keypass_tmax1");
            }
        } else if (pc.getUsage() == WSPasswordCallback.DECRYPT) {
            if (userId.equals("16c73ab6-b892-458f-abf5-2f875f74882e")) {
                pc.setPassword("security");
            }
            if (userId.equals("key_tmax1")) {
                pc.setPassword("keypass_tmax1");
            } 
        } else if(pc.getUsage() == WSPasswordCallback.SIGNATURE) {
            if (userId.equals("16c73ab6-b892-458f-abf5-2f875f74882e")) {
                pc.setPassword("security");
            }
            if (userId.equals("key_tmax1")) {
                pc.setPassword("keypass_tmax1");
            }
        }
    }
}


WSPasswordCallback.getUsage()는 인자로 넘겨 받는 Callback 파라미터가 어떤 경우에 사용되기 위한 것인지에 대한 값을 리턴한다. 사용자명 토큰의 암호를 설정하려면 USERNAME_TOKEN이라는 값을 가지고 있고, 암호화된 메시지를 해독하기 위해 사용할 개인 키에 대한 암호를 설정하려면 DECRYPT 값을 리턴한다. 그 외에도 서명을 하기 위해 필요한 개인 키를 Keystore에서 가져오기 위한 암호를 설정하거나 세션 키를 설정하려 할 경우에도 이러한 구현이 필요하다.

이렇게 생성된 Callback 클래스는 jeus-webservices-dd.xml이나 jeus-web-dd.xml의 <password-callback-class>에 클래스 이름을 설정함으로써 보안에 적용이 가능하다.

27.3.5. JEUS 웹 서비스 서버 보안 적용 예제

스트링을 주고받는 웹 서비스를 보안을 적용은 다음의 과정으로 구현되어야 한다.

  1. SOAP 메시지를 암호화하고 서명하는 데 필요한 Keystore 생성한다.

  2. Java 클래스 작성한다.

  3. 웹 서비 스 DD를 작성한다.

  4. 패키징과 배치를 한다.

본 절에서는 각 과정에 대해서 설명한다.

27.3.5.1. Keystore 생성

SOAP 메시지를 암호화하고 서명하는 데 필요한 키의 생성이 필요하며 Java에서 제공하는 Keytool을 이용하여 간단하게 구현할 수 있다. keytool 외에도 여러 가지 방법이 있지만, 여기에서는 keytool을 이용하도록 한다. 좀 더 상세한 설명은 keytool의 도움말을 참고한다.

샘플이 존재하는 폴더에서 keygen을 수행하면 다음과 같은 작업을 수행한다.

  1. server-keystore.jks라는 Keystore 파일을 생성하고 Keystore의 암호는 keystore_password로 한다. Keystore에는 JEUS_SERVER라는 별칭을 가진 개인 키를 RSA 알고리즘으로 생성하며 개인 키의 암호는 key_password이다. 이때 JEUS_SERVER라는 개인 키의 공개 키도 쌍으로 생성된다.

    $ keytool -genkey -alias JEUS_SERVER -keyalg rsa -keypass
    key_password -keystore server-keystore.jks -storepass
    keystore_password
  2. client-keystore.jks라는 Keystore 파일을 생성하고 Keystore의 암호는 keystore_password로 한다. Keystore에는 JEUS_CLIENT라는 별칭을 가진 개인 키를 RSA 알고리즘으로 생성하며 개인 키의 암호는 key_password이다. 이때 JEUS_CLIENT라는 개인 키의 공개 키도 쌍으로 생성된다.

    $ keytool -genkey -alias JEUS_CLIENT -keyalg rsa -keypass
    key_password -keystore client-keystore.jks -storepass
    keystore_password
  3. 서버의 Keystore로 사용할 server-keystore.jks라는 Keystore에서 JEUS_SERVER의 공개 키를 꺼낸다.

    $ keytool -export -alias JEUS_SERVER -storepass keystore_password
    -keystore server-keystore.jks -file jeus_server.cert
  4. 클라이언트 Keystore인 client-keystore.jks에 JEUS_SERVER라는 별칭으로 저장한다.

    $ keytool -import -file jeus_server.cert -keystore
    client-keystore.jks -storepass keystore_password -alias JEUS_SERVER
  5. 클라이언트 Keystore로 사용할 client-keystore.jks라는 Keystore에서 JEUS_CLIENT의 공개 키를 꺼낸다.

    $ keytool -export -alias JEUS_CLIENT -storepass keystore_password
    -keystore client-keystore.jks -file jeus_client.cert
  6. 서버 Keystore인 server-keystore.jks에 JEUS_CLIENT라는 별칭으로 저장한다.

    $ keytool -import -file jeus_client.cert -keystore
    server-keystore.jks -storepass keystore_password -alias JEUS_CLIENT

27.3.5.2. Java 클래스 작성

기본적인 웹 서비스 구현은 보안이 적용되지 않았을 경우와 차이점이 없다.

[예 27.4] << Ping.java >>

package ping;

public interface Ping extends java.rmi.Remote {
    public String ping(String arg) throws
        java.rmi.RemoteException;
}


[예 27.5] << PingImpl.java >>

package ping;

public class PingImpl implements Ping {
    public String ping(String arg) throws
        java.rmi.RemoteException {
        return arg;
    }
}


여기에 추가적으로 개인 키를 꺼내오거나 사용자명 토큰의 암호를 설정하기 위한 Callback 클래스를 생성해야 한다.

[예 27.6] << PingPWCallback.java >>

package ping;

import com.tmax.ws.security.WSPasswordCallback;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;

public class PingPWCallback implements CallbackHandler {
    public void handle(Callback[] callbacks) throws
        IOException, UnsupportedCallbackException {
        for ( int i = 0 ; i<callbacks.length; i++) {
            if(callbacks[i] instanceof WSPasswordCallback) {
                WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];

                if(pc.getUsage()== WSPasswordCallback.USERNAME_TOKEN) {
                    pc.setPassword("usertoken_password");
                }

                if(pc.getUsage()== WSPasswordCallback.DECRYPT){
                    pc.setPassword("key_password");
                }

                if(pc.getUsage()== WSPasswordCallback.SIGNATURE) {
                    pc.setPassword("key_password");
                }
            }
        }
    }
}


27.3.5.3. DD 파일 작성

Keystore를 생성하고 Java 클래스 작성이 완료되면 웹 서비스 DD를 작성해야 한다. 다른 부분은 일반적인 웹 서비스 DD 작성과 동일하며, JEUS 특정 설정을 담당하는 jeus-webservices-dd.xml에서만 추가 작업을 한다.

다음은 서버에서 클라이언트의 메시지를 처리하는 부분의 설정이다. 파일 전체의 내용은 샘플을 참고한다.

[예 27.7] << jeus-webservices-dd.xml >>

. . .
<security>
    <request-receiver>
        <action-list>
            UsernameToken Signature Encrypt
        </action-list>
        <password-callback-class>
            ping.PingPWCallback
        </password-callback-class>
        <decryption>
            <keystore>
                <key-type>jks</key-type>
                <keystore-password>
                    keystore_password
                </keystore-password>
                <keystore-filename>
                    server-keystore.jks
                </keystore-filename>
            </keystore>
        </decryption>
        <signature-verification>
            <keystore>
                <key-type>jks</key-type>
                <keystore-password>
                    keystore_password
                </keystore-password>
                <keystore-filename>
                    server-keystore.jks
                </keystore-filename>
            </keystore>
        </signature-verification>
    </request-receiver>
    . . .
</security>
. . .


27.3.5.4. 패키징과 배치

웹 서비스 보안 모듈의 패키징과 배치 방식은 기존의 웹 서비스 모듈의 방식과 크게 다르지 않다. 추가적으로 Keystore(server-keystore.jks)를 컨텍스트의 클래스 경로 안에 위치시켜야 하는 점이 다르다. 또는 jeus-webservices-dd.xml의 <keystore-filename>에 절대 경로를 포함한 Keystore 파일 이름을 적어주는 방법도 있다.

샘플 홈인 pingSecurityService 디렉터리에서 다음과 같이 입력하면 패키징 작업과 패키징된 모듈에 대해 JEUS 서버 배치 작업이 완료된다.

$ ant

이제 보안이 적용된 웹 서비스를 시작할 수 있다.

27.3.6. JEUS 웹 서비스 클라이언트 보안 적용 예제

JEUS의 웹 서비스 Java EE 클라이언트의 경우도 서버의 설정과 유사하게 일반적인 웹 서비스 클라이언트 생성 작업에 Callback 클래스와 설정 파일 작업, Keystore 패키징 작업이 추가적으로 더해진다.

JEUS 웹 서비스 클라이언트 보안 적용은 다음의 과정으로 구현되어야 한다.

  1. SOAP 메시지를 암호화하고 서명하는 데 필요한 Keystore 생성한다.

  2. Java 클래스 작성한다.

  3. 웹 서비 스 DD를 작성한다.

  4. 패키징과 배치를 한다.

본 절에서는 각 과정에 대해서 설명한다.

27.3.6.1. Keystore 생성

Keystore는 “27.3.5.1. Keystore 생성”에서 생성한 Keystore 중 client-keystore.jks를 사용한다.

27.3.6.2. Java 클래스 작성

다음은 클라이언트인 pingClient.jsp 파일의 일부이다. 웹 서비스의 보안 적용은 웹 서비스의 포트 단위로 적용되므로 반드시 호출할 포트의 이름을 명시해야 한다.

[예 27.8] << pingClient.jsp >>

. . . 
InitialContext jndiContext = new InitialContext();

Service service = (Service)jndiContext.lookup(
"java:comp/env/service/PingSecurityService");

QName portName = new QName("urn:PingSecurityService","PingPort");
java.rmi.Remote port = service.getPort(portName, Ping.class);

Ping pingPort = (Ping)port;
ret = pingPort.ping(msgToSend);
. . .


클라이언트도 서버 설정과 마찬가지로 Callback 클래스를 생성한다. 여기서 사용하는 Callback 클래스는 서버 설정할 때 생성했던 Callback 클래스를 재사용하도록 한다.

27.3.6.3. DD 파일 작성

다른 설정 파일은 일반적인 웹 서비스 클라이언트 DD 파일 작성과 동일하며, JEUS 특성 설정을 담당하는 jeus-web-dd.xml에서만 추가 작업을 한다.

다음은 클라이언트에서 서버로 전송할 메시지를 처리하는 부분의 설정이다. 파일 전체의 내용은 샘플을 참고한다.

[예 27.9] << jeus-web-dd.xml >>

. . .
<security>
    <request-sender>
        <action-list>
            UsernameToken Signature Encrypt
        </action-list>
        <password-callback-class>
            ping.PingPWCallback
        </password-callback-class>
        <user>JEUS_CLIENT</user>
        <signature-infos>
            <signature-info>
                <keyIdentifier>DirectReference</keyIdentifier>
                <keystore>
                    <key-type>jks</key-type>
                    <keystore-password>
                        keystore_password
                    </keystore-password>
                    <keystore-filename>
                        client-keystore.jks
                    </keystore-filename>
                </keystore>
            </signature-info>
        </signature-infos>
        <encryption-infos>
            <encryption-info>
                <encryptionUser>JEUS_SERVER</encryptionUser>
                <keyIdentifier>DirectReference</keyIdentifier>
                <keystore>
                    <key-type>jks</key-type>
                    <keystore-password>
                        keystore_password
                    </keystore-password>
                    <keystore-filename>
                        client-keystore.jks
                    </keystore-filename>
                </keystore>
            </encryption-info>
        </encryption-infos>
    </request-sender>
    . . .
</security>
. . .


27.3.6.4. 패키징과 배치

웹 서비스 Java EE 클라이언트 보안 모듈의 패키징과 배치 방식은 기존의 웹 서비스 Java EE 클라이언트 모듈의 방식과 크게 다르지 않다. 추가적으로 Keystore(client-keystore.jks)를 컨텍스트의 클래스 경로 안에 위치시켜야 하는 점이 다르다. 또는 jeus-web-dd.xml에 <keystore-filename>에 절대 경로를 포함한 Keystore 파일 이름을 적어주는 방법도 있다.

샘플 홈인 pingSecurityServiceJavaeeClient 디렉터리에서 다음과 같이 입력하면 패키징 작업과 JEUS 서버 배치 작업이 완료된다.

$ ant

패키징하여 배치까지 완료되면 보안이 적용된 웹 서비스 클라이언트를 시작할 수 있다.

27.3.7. 보안 API를 이용한 JEUS 웹 서비스 클라이언트의 작성

JEUS 웹 서비스는 Java EE 환경과 Java SE 환경에서 모두 적용이 가능한 웹 서비스 클라이언트 보안 API를 제공하여 보다 쉽게 웹 서비스 클라이언트 작성을 가능하게 한다.

다음은 API를 이용한 웹 서비스 보안 클라이언트이다.

[예 27.10] << pingClient.jsp >>

<%@ page language="java" %>
<%@ page import="javax.naming.*" %>
<%@ page import="javax.rmi.*" %>
<%@ page import="java.rmi.RemoteException" %>
<%@ page import="java.util.*" %>

<%@ page import="javax.naming.InitialContext" %>
<%@ page import="javax.xml.rpc.Service" %>
<%@ page import="javax.xml.namespace.QName" %>

<%@ page import="jeus.webservices.wssecurity.SecurityConfiguration"%>
<%@ page import="jeus.webservices.wssecurity.Keystore" %>

<%@ page import="ping.*" %>
<%@ page errorPage="/error.html" %>

<%! String msgToSend = "msg_sent_by_jspClient";
    String ret=null;
    String exceptionString="";
%>

<%
    try {

(1)     Keystore keystore = new Keystore ("JKS", "keystore_password", 
                           "client-keystore.jks");
        Keystore truststore = new Keystore ("JKS","keystore_password", 
                           "client-keystore.jks");

        InitialContext jndiContext = new InitialContext();
            
        Service service = 
          (Service)jndiContext.lookup("java:comp/env/service/PingSecurityService");
        QName portName = new QName("urn:PingSecurityService", "PingPort");

(2)     SecurityConfiguration sconfig = 
            new SecurityConfiguration(service, portName);
(3)     sconfig.setUsername("JEUS_CLIENT");
(4)     sconfig.setRequestPasswordCallbackClass("ping.PingPWCallback");
            
(5)     sconfig.addUTRequest(true, true);
(6)     sconfig.addSignRequest(null, "DirectReference", keystore);
(7)     sconfig.addEncryptRequest(null, "DirectReference", 
            truststore, "JEUS_SERVER", null);

(4)     sconfig.setResponsePasswordCallbackClass("ping.PingPWCallback");
        sconfig.addUTResponse();
        sconfig.addSignResponse(truststore);
        sconfig.addDecryptResponse(keystore);

        java.rmi.Remote port = service.getPort(portName, Ping.class);
        Ping pingPort = (Ping)port;
            
        ((javax.xml.rpc.Stub)pingPort)._setProperty(
            javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY,
            "http://localhost:8088/PingSecurity" + "/PingSecurityService");
            
        System.out.println("Send : " + msgToSend);
        ret = pingPort.ping(msgToSend);
        System.out.println("You received : " + ret);
    } catch (Exception e) {
        exceptionString = e.toString();
        e.printStackTrace();
    }
%>
<%= "Result is " + ret + "......" 
%>
<%= exceptionString
%>


위의 예제 중 굵은 글자로 표시된 부분이 웹 서비스 보안 설정에 관한 부분이다.

보안 설정을 위해 jeus.webservices.wssecurity.SecurityConfiguration 객체를 생성하고, 보안 요청 메시지를 작성하기 위해서는 기본적으로 사용자 이름과 패스워드 Callback 클래스를 지정해야 한다. Callback 클래스 생성에 관해서는 “27.3.4. 패스워드 Callback 클래스 생성”을 참고한다.

그 외에 사용자 이름 토큰이나 서명, 암호화에 관한 설정을 추가할 때 각각의 API를 이용하여 추가할 수 있다. 이때 Keystore에 대한 설정이 필요할 경우에는 jeus.webservices.wssecurity.Keystore 클래스를 생성하여 설정이 가능하다.

  • (1) Keystore 객체 생성

    웹 서비스 보안 API를 적용하기 위해서는 서명 작업이나 암호화 작업의 진행을 위해 Keystore 객체의 생성이 필요하다.

    public Keystore(String keyType, String keystorePassword, String keystoreFilename)
    파라미터설명
    keystoreFilenameKeystore의 파일 이름을 입력한다.
    keystorePasswordKeystore 파일의 암호를 입력한다.
    keyTypeKeystore가 저장하고 있는 키의 타입을 입력한다. 지원되는 키의 타입은 'JKS'나 'PKCS12' 둘 중에 하나이다.
  • (2) SecurityConfiguration 객체 생성

    웹 서비스 보안 API를 적용하기 위해 가장 먼저 생성해야 하는 객체이다.

    public SecurityConfiguration(javax.xml.rpc.Service service,QName portName) 
              throws ConfigurationException
    파라미터설명
    serviceJava EE 클라이언트에서 작성할 경우에는 JNDI를 통해 얻어오는 Service 객체를 넣어주고, Java SE 클라이언트에서 작성할 경우에는 Service_Impl() 객체를 통해 생성한 객체를 인자로 넣어준다.
    portNameWSDL에 공개된 포트의 QName을 넣어준다.
  • (3) 사용자 이름 설정

    웹 서비스 보안 요청 메시지를 작성하기 위해서는 사용자명 토큰에 들어갈 이름이나 서명에 사용될 개인 키의 이름이 필요하며 이를 이 함수를 통해 설정할 수 있다.

    public void setUsername(String username)
  • (4) Callback 클래스 이름 설정

    웹 서비스 보안 요청이나 응답 메시지를 주고받는 과정 중 사용자명 토큰에 사용되는 암호 설정이나, 개인 키에 대한 암호 설정을 할 경우 패스워드 Callback 클래스들을 생성해야 하며, 생성된 Callback 클래스의 이름을 이 함수들을 사용하여 지정한다.

    public void setRequestPasswordCallbackClass(String classname)
    public void setResponsePasswordCallbackClass(String classname)
  • (5) UsernameToken 처리

    다음 함수는 웹 서비스 보안 요청 메시지에 UsernameToken element를 추가할 경우 사용한다.

    public void addUTRequest(boolean addNonceCreated, boolean passwordDigest)

    패스워드는 평문이나 다이제스트 형태로 생성될 수 있으며, 평문으로 생성할 경우에는 Nonce와 Created 항목을 추가할 것인지를 선택할 수 있다.

    다음 함수는 웹 서비스 보안 응답 메시지에 UsernameToken element가 있기를 기대하는 경우에 사용한다.

    public void addUTResonse()
  • (6) 서명 처리

    다음 함수는 웹 서비스 보안 요청 메시지에 특정 부분에 서명할 때 사용한다.

    public void addSignRequest(QName signPart, String keyIdentifier, 
                           Keystore keystore)
           throws SecurityConfigurationException
    파라미터설명
    signPartnull로 설정하면 기본적으로 SOAP 메시지의 body 부분을 기본적으로 서명한다.
    keyIdentifier

    다음 중 하나를 입력한다.

    • IssuerSerial

    • DirectReference

    • SKIKeyIdentifier

    • X509KeyIdentifier

    keystore서명에 필요한 개인 키를 저장하고 있는 Keystore 객체를 입력한다.
  • (7) 암호화 처리

    다음 함수는 웹 서비스 보안 요청 메시지에 특정 부분을 암호화할 때 사용한다.

    public void addEncryptRequest(QName encPart, String keyIdentifier,
        Keystore keystore, String encryptUser, String algorithm)
        throws SecurityConfigurationException
    파라미터설명
    encPart

    암호화하는 SOAP 메시지의 부분으로 QName 형태로 입력한다.

    만약 null로 설정될 경우 기본적으로 SOAP 메시지의 body 부분을 기본적으로 암호화한다.

    keyIdentifier

    다음 중 하나를 입력한다.

    • IssuerSerial

    • DirectReference

    • SKIKeyIdentifier

    • X509KeyIdentifier

    • EmbeddedKeyName

    keystore암호화에 필요한 공개 키를 저장하고 있는 Keystore 객체를 입력한다.
    encryptUser암호화에 사용할 공개 키의 별칭을 입력한다.
    algorithm

    암호화에 사용될 알고리즘으로 다음 중에 하나를 입력한다.

    • AES_128

    • AES_256

    • TRIPLE_DES

    • AES_192

    다음 함수는 웹 서비스 보안 요청 메시지에 특정 부분을 암호화하고자 할 때 사용하며, 특히 특정 Callback 클래스에 Byte의 배열로 키를 설정하여 그 키를 이름으로 불러내려 할 때, 이 함수를 사용한다.

    public void addEncryptRequest(QName encPart,
        String keyIdentifier, String embeddedKeyCallbackClass,
        String keyName, String encryptUser, String algorithm)

    다음 함수는 웹 서비스 보안 응답 메시지에 암호화 된 부분이 있기를 기대할 때 사용한다. keystore 파라미터에는 암호화를 풀기 위한 개인 키를 가지고 있는 Keystore 객체를 입력한다.

    public void addDecryptResponse(Keystore keystore)

[참고]

다음은 본 예제에는 적용되지 않았으나 추가로 사용할 수 있는 기능에 대한 설명이다.

  • TimeStamp 처리

    다음 함수는 웹 서비스 보안 요청 메시지에 TimeStamp element를 추가할 경우 사용한다.

    public void addTimeStampRequest()

    다음 함수는 웹 서비스 보안 요청 메시지에 유효 기간을 별도로 설정할 경우 사용한다. 초(second)단위이며, 별도로 설정하지 않았을 경우 기본 설정값은 300초이다.

    public void addTimeStampRequest(int timeToLive)

    다음 함수는 웹 서비스 보안 응답 메시지에 TimeStamp element가 있기를 기대하는 경우 사용한다.

    public void addTimeStampResponse()

    다음 함수는 웹 서비스 보안 응답 메시지의 TimeStamp element에 설정된 유효 기간이 인자로 입력한 시간 이내여야 한다.

    public void addTimeStampResponse(int timeToLive)

27.4. 접근 제어 설정

JEUS 웹 서비스는 접근이 허용된 사용자만이 웹 서비스를 호출할 수 있도록 접근 제어 설정을 할 수 있으며, 웹 서비스 Back-end에 따라 각각 설정법이 다르다. 본 절에서는 웹 서비스 접근 제어 설정법과 접근 제어 설정이 된 웹 서비스를 호출하는 방법에 대해 설명한다.

27.4.1. Java 클래스 웹 서비스 접근 제어 설정

특정한 URL로의 접근을 제한함으로써 웹 서비스로의 접근을 제한할 수 있으며, 이를 위해서는 web.xml과 jeus-web-dd.xml 같은 웹 응용 프로그램의 DD에 보안 정보를 추가해야 한다. 또한 보안 도메인의 설정도 필요하다.

보안 도메인 설정

접근 제어 설정을 하기 위해서는 먼저 사용자 등록이 필요하다.

다음 경로의 accounts.xml에 사용자를 등록한다.

JEUS_HOME/config/{NODE_NAME}/security/{SECURITY_DOMAIN_NAME}/accounts.xml

다음은 사용자 등록의 예로 사용자의 이름은 'jeus'이고, 암호는 'jeus'이며, 암호는 base64 인코딩하여 등록한다.

[예 27.11] << accounts.xml >>

<accounts xmlns="http://www.tmaxsoft.com/xml/ns/jeus">
    <users>
        <user>
            <name>jeus</name>
            <password>{SHA}McbQlyhI3yiOG1HGTg8DQVWkyhg=</password>
        </user>
    </users>
</accounts>


DD 파일 작성

jeus-web-dd.xml에 다음과 같이 <role-mapping>을 추가한다.

[예 27.12] << jeus-web-dd.xml >>

<jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus">
    <docbase>BA_DocLitEchoService</docbase>
    <role-mapping>
        <role-permission>
            <principal>jeus</principal>
            <role>Administrator</role>
        </role-permission>
    </role-mapping>
</jeus-web-dd>


그리고 web.xml에 다음과 같이 보안 관련 설정을 추가한다.

[예 27.13] << web.xml >>

<web-app…>
    . . .
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>MySecureBit</web-resource-name>
            <url-pattern>/BA_DocLitEchoService</url-pattern>
            <http-method>POST</http-method>
            <http-method>GET</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>Administrator</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>   
    
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>default</realm-name>
    </login-config>    
</web-app>


위와 같이 설정하면 URL이 /BA_DocLitEchoService인 모든 요청에 대해 사용자가 'jeus'이며 암호가 'jeus'일 경우에만 접근이 허용된다.

27.4.2. EJB 웹 서비스의 접근 제어 설정

EJB 웹 서비스의 접근 제어 또한 Java 클래스 웹 서비스의 접근 제어와 마찬가지로 특정한 URL로의 접근을 제한함으로써 웹 서비스로의 접근을 제한할 수 있다. 이를 위해서는 ejb-jar.xml과 jeus-ejb-dd.xml 같은 EJB DD와 jeus-webservices-dd.xml과 같은 웹 서비스 DD에 보안 정보를 추가해야 한다. 또한 보안 도메인의 설정이 필요하다.

보안 도메인 설정

접근 제어 설정을 하기 위해서는 먼저 사용자의 등록이 필요하다.

다음 경로의 accounts.xml에 사용자를 등록한다.

JEUS_HOME/config/{NODE_NAME}/security/{SECURITY_DOMAIN_NAME}/accounts.xml

다음은 사용자 등록의 예로 사용자의 이름은 'jeus'이고, 암호는 'jeus'이며, 암호는 base64 인코딩하여 등록한다.

[예 27.14] << accounts.xml >>

<accounts xmlns="http://www.tmaxsoft.com/xml/ns/jeus">
    <users>
        <user>
            <name>jeus</name>
            <password>{SHA}McbQlyhI3yiOG1HGTg8DQVWkyhg=</password>
        </user>
    </users>
</accounts>


DD 파일 작성

jeus-ejb-dd.xml에 다음과 같이 <role-permission>을 추가한다.

[예 27.15] << jeus-ejb-dd.xml >>

<jeus-ejb-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus">
    <module-info>        
        <role-permission>
            <principal>jeus</principal>
            <role>Administrator</role>
        </role-permission>
    </module-info>
    <beanlist>
        …
    </beanlist>
</jeus-ejb-dd>


그리고 ejb-jar.xml에 다음과 같이 보안 관련 설정을 추가한다.

[예 27.16] << ejb-jar.xml >>

<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar …>
    <display-name>AddressEjb</display-name>
    <enterprise-beans>
        …
    </enterprise-beans>
    <assembly-descriptor>
        <security-role>
            <role-name>Administrator</role-name>
        </security-role>
        <method-permission>
            <role-name>Administrator</role-name>
            <method>
                <ejb-name>AddressEJB</ejb-name>
                <method-intf>ServiceEndpoint</method-intf>
                <method-name>listAll</method-name>                
            </method>
        </method-permission>
    </assembly-descriptor>
</ejb-jar>


추가적으로 다음과 같은 설정을 웹 서비스 DD에 할 수 있으며, 별도로 하지 않을 경우에는 기본적으로 설정된 값으로 수행된다. SSL을 사용하여 주고받는 데이터의 무결성을 보장하려면 <ejb-transport-guarantee>를 'INTEGRAL' 값을 설정하고, 데이터의 기밀성과 무결성을 모두 보장하려면 'CONFIDENTIAL'로 설정해야 한다. 물론 이 경우에는 HTTPS로 통신을 주고받도록 별도의 HTTP 리스너의 설정이 필요하다.

[예 27.17] << jeus-webservices-dd.xml >>

<jeus-webservices-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus">
    <ejb-context-path>webservice</ejb-context-path>
    <ejb-login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>default</realm-name>
    </ejb-login-config>
    <service>
        <webservice-description-name>…</webservice-description-name>
        <port>
            <port-component-name>…</port-component-name>
            <ejb-endpoint-url>… </ejb-endpoint-url>
            <ejb-transport-guarantee>
                CONFIDENTIAL
            </ejb-transport-guarantee>
        </port>
    </service>
</jeus-webservices-dd>


27.4.3. 접근 제어가 설정된 웹 서비스 호출

접근 제어(Basic Authentication)가 설정된 웹 서비스의 호출을 하기 위해서는 WSDL에 접근하여 그 내용을 바탕으로 Stub을 생성하고, Stub을 이용하여 웹 서비스를 호출해야 하는데, 이 경우에 사용자 이름과 암호를 요구할 경우 설정해 주는 작업이 필요하다.

WSDL로부터 Stub 생성

콘솔 툴을 이용할 경우 다음과 같이 설정한다.

wsdl2java -gen:client -username jeus -password jeus 
http://localhost:8088/BA_DocLitEchoService/BA_DocLitEchoService?wsdl

접근 제어가 설정된 웹 서비스의 클라이언트 작성

JAX-RPC 웹 서비스 클라이언트가 스스로 인증하기 위해서는 다음과 같은 2가지의 설정이 클라이언트 프로그램 내에 삽입되어야 한다.

  • javax.xml.rpc.Stub.USERNAME_PROPERTY

  • javax.xml.rpc.Stub.PASSWORD_PROPERTY

다음 예는 실제로 프로그램 내에 어떻게 위 설정들이 삽입될 수 있는지를 보여준다.

Echo port = // … 서비스 Endpoint 인터페이스의 획득
((javax.xml.rpc.Stub) port)._setProperty(javax.xml.rpc.Stub.USERNAME_PROPERTY, 
      "jeus");
((javax.xml.rpc.Stub) port)._setProperty(javax.xml.rpc.Stub.PASSWORD_PROPERTY, 
      "jeus");
String s = port.echoString("JEUS");