2008년 3월 5일 수요일

SSL로 JAX-WS 기반 웹 서비스 이용하기

Java EE 5에서는 JAX-WS에 기반을 둔 웹 서비스를 EJB 엔드포인트나 서블릿 엔드포인트로 구현할 수 있다. EJB 엔드포인트로 구현된 JAX-WS 기반 웹 서비스의 예는 테크 팁 EJB 3.0을 이용하여 웹 서비스 개발하기에서 찾아볼 수 있으며, 서블릿 엔드포인트로 구현된 JAX-WS 기반 웹 서비스에 대한 예는 테크 팁 JAX-WS를 이용하여 웹 서비스 개발하기에 나와 있다.
웹 서비스 구현 방식과 관계없이, 웹 서비스를 이용하는 경우를 포함한 대부분의 엔터프라이즈 애플리케이션은 반드시 안전한 환경에서 실행되어야 한다. TSL(Transport Layer Security)/SSL(Secure Sockets Layer)은 인증, 메시지 무결성 및 비밀 유지를 위해 사용할 수 있는 point-to-point 보안 전송 메커니즘으로, 이 TLS/SSL (본 팁에서는 간단히 ‘SSL’로 표기)은 대부분의 엔터프라이즈 애플리케이션 환경의 보안 요구사항을 충족할 뿐 아니라 실제로 널리 채택되어 사용되고 있다.
본 테크 팁은 SSL로 실행되는 JAX-WS 기반 웹 서비스를 구성하는 방법과 애플리케이션 클라이언트에서의 웹 서비스 액세스 방법을 다룬다. 첨부된 예제 패키지에서는 SSL을 이용하여 JAX-WS 웹 서비스에 액세스하는 자바 클라이언트를 예시하고 있다. 이와 더불어 EJB 및 서블릿 엔드포인트로 구현되는 웹 서비스의 예제가 제공되며, 예제는 GlassFish라고 불리는 Java EE 5의 오픈 소스 레퍼런스 구현을 사용한다. GlassFish는 GlassFish 커뮤니티 다운로드 페이지에서 다운로드할 수 있다.
웹 서비스 엔드포인트를 위한 클래스 작성
먼저 웹 서비스를 위한 자바 클래스를 작성해 보기로 하자. SSL은 웹 서비스 엔드포인트의 자바 코드에 영향을 미치지 않으며, SSL을 이용하거나 이용하지 않는 웹 서비스 모두에 동일한 코드가 적용된다.
Java EE 5에서는 주석을 이용하여 손쉽게 JAX-WS 웹 서비스를 구성할 수 있다. 다음은 EJB 엔드포인트로 구현된 웹 서비스의 예제이다.
   package ejbws;

   import javax.annotation.Resource;
   import javax.ejb.Stateless;
   import javax.jws.WebService;
   import javax.xml.ws.WebServiceContext;

   @Stateless
   @WebService
   public class HelloEjb {
       @Resource WebServiceContext wsContext;

       public String hello(String msg) {
           return "Ejb WS: " + wsContext.getUserPrincipal() 
           + ": " + msg;
       }
   }
The @Stateless 주석은 클래스를 stateless 세션 빈으로 마크하고 @WebService 주석은 클래스를 웹 서비스로 마크한다. @Resource 주석은 클래스가 지니는 리소스 의존성—본질적으로, 클래스에 어떤 리소스가 필요한가—을 선언하는 데 사용되며, 이어서 이 리소스들은 엔드포인트 구현에 삽입된다. 이 예제에서 주석은 WebServiceContext에 대한 리소스 의존성을 식별한다. 한편 클래스는 관련 보안 정보와 같은 요청에 관한 컨텍스트 정보를 얻기 위해 WebServiceContext를 필요로 한다.
다음은 동일한 웹 서비스가 서블릿 엔드포인트로 구현된 예제이다.
   package servletws;

   import javax.annotation.Resource;
   import javax.jws.WebService;
   import javax.xml.ws.WebServiceContext;

   @WebService
   public class HelloServlet {
       @Resource WebServiceContext wsContext;

       public String hello(String msg) {
           return "Servlet WS: " + wsContext.getUserPrincipal() 
           + ": " + msg;
       }
   }
배포 서술자에 보안 정보 명시
EJB 엔드포인트로 구현된 웹 서비스에서 SSL을 이용하려면 벤더별 배포 서술자(본 팁의 경우에는 sun-ejb-jar.xml)에 보안 정보를 명시해야 한다. 단, 서블릿으로 구현된 웹 서비스의 경우에는 web.xml 서술자에 보안 정보를 명시하도록 한다.
SSL을 통한 보안 통신에 있어서 중요 사항 중 한 가지는 서버 인증, 즉 서버의 아이덴티티를 클라이언트에 확인시키는 일이다. 또 중요한 측면은 바로 클라이언트 인증인데, 이 경우에는 서버가 클라이언트의 아이덴티티를 확인하게 된다. SSL에서는 서버 인증이나 서버와 클라이언트가 결합된 인증이 허용된다(클라이언트 인증만은 허용되지 않음). 본 팁에서는 서버와 클라이언트가 결합된 인증에 대해 ‘상호 인증’이라는 용어를 사용하기로 한다. (단, 타 문서에서는 상호 인증이 다른 의미로 사용될 수 있다는 점에 유의할 것. 예를 들어, 어떤 문서에서는 클라이언트 인증이 상호 인증과 동의어로 사용되기도 한다.)
SSL 서버 인증을 인에이블하려면 <transport-guarantee> 엘리먼트를 CONFIDENTIAL로 설정해야 하는데, EJB 엔드포인트로 구현된 웹 서비스의 경우에는 sun-ejb-jar.xml 배포 서술자 내의 엘리먼트를 설정한다. 또 서블릿으로 구현된 웹 서비스의 경우에는 web.xmll 배포 서술자 내의 엘리먼트를 설정한다. 예를 들어, HelloEjb 웹 서비스에서는 sun-ejb-jar.xml 배포 서술자 내의 <transport-guarantee> 엘리먼트가 다음과 같은 형태를 띠어야 한다.
   <ejb>
     <ejb-name>HelloEjb</ejb-name>
     <webservice-endpoint>
       <port-component-name>HelloEjb</port-component-name>
       <transport-guarantee>CONFIDENTIAL</transport-guarantee>
     </webservice-endpoint>
   </ejb>
HelloServletService 서비스의 경우에는 web.xml 배포 서술자 내의 <transport-guarantee> 엘리먼트는 다음과 같은 형태를 띠어야 한다.
   <security-constraint>
     <web-resource-collection>
       <web-resource-name>Secure Area</web-resource-name>
       <url-pattern>/HelloServletService/HelloServlet
       </url-pattern>
       <http-method>POST</http-method>
     </web-resource-collection>
     <auth-constraint>
       <role-name>EMPLOYEE</role-name>
     </auth-constraint>
     <user-data-constraint>
       <transport-guarantee>CONFIDENTIAL</transport-guarantee>
     </user-data-constraint>
   </security-constraint>
web.xmlPOST<http-method> 엘리먼트 값에 특히 유의할 것. JSR-109 "Implementing Enterprise Web Services"에는 서비스 엔드포인트 인증이 반드시 POSThttp-method 엘리먼트 값을 이용해서 정의되어야 한다고 명시되어 있다.
GlassFish에서 WSDL 파일은 SSL나 SSL 상호 인증을 통해 엔드포인트의 SSL에 의해 보호된다. SSL 상호 인증의 경우에는 <login-config> 엘리먼트의 <auth-method> 서브엘리먼트를 CLIENT-CERT로 설정해야 하고, 아울러 <transport-guarantee> 엘리먼트를 CONFIDENTIAL로 설정해야 한다. 예를 들어 sun-ejb-jar.xml의 경우는 다음과 같다.
   <ejb>
     <ejb-name>HelloEjb</ejb-name>
     <webservice-endpoint>
       <port-component-name>HelloEjb</port-component-name>
       <login-config>
         <auth-method>CLIENT-CERT</auth-method>
         <realm>certificate</realm>
       </login-config>
       <transport-guarantee>CONFIDENTIAL</transport-guarantee>
     </webservice-endpoint>
  </ejb>
   <security-constraint>
     <web-resource-collection>
       <web-resource-name>Secure Area</web-resource-name>
       <url-pattern>/HelloServletService/HelloServlet
       </url-pattern>
       <http-method>POST</http-method>
     </web-resource-collection>
     <auth-constraint>
       </role-name>EMPLOYEE</role-name>
     </auth-constraint>
     <user-data-constraint>
       <transport-guarantee>CONFIDENTIAL</transport-guarantee>
     </user-data-constraint>
   </security-constraint>
   <login-config>
     <auth-method>CLIENT-CERT</auth-method>
     <realm-name>certificate</realm-name>
   </login-config>
클라이언트 프로그램에는 배포 서술자가 필요하지 않다.
웹 서비스 애플리케이션의 패키징 및 배포
SSL을 이용하는 웹 서비스 애플리케이션의 패키징 및 배포는 SSL을 이용하지 않는 웹 서비스 애플리케이션의 경우와 동일하다. 하지만, 웹 서비스가 엔터프라이즈 아카이브(즉, .ear 파일) 내의 서블릿 엔드포인트로 구현되는 경우에는 웹 애플리케이션에 대해 context-root를 명시하기 위한 application.xml 파일을 포함시켜야 한다는 점을 유념해야 한다.
클라이언트 작성
웹 서비스를 배포한 후에는 클라이언트 프로그램으로 액세스할 수 있다. SSL을 이용하는 웹 서비스 애플리케이션을 위한 클라이언트 프로그램은 SSL을 이용하지 않는 경우와 사실상 동일하다. 주된 차이점은 인터넷 프로토콜로 HTTP를 사용하는 대신 HTTPS를 사용해야 한다는 사실이다. 클라이언트에서 @WebServiceRef 주석을 이용하여 웹 서비스에 대한 레퍼런스를 선언한다. 이 때, @WebServiceRef 내의 wsdlLocation 파라미터 값은 레퍼런스된 서비스를 위한 WSDL 파일의 위치를 가리키는 URL이다. 따라서 SSL을 이용하는 웹 서비스 클라이언트의 경우, @WebServiceRef 주석 내의 wsdlLocation 파라미터는 HTTPS URL을 명시해야 한다. 예:
   @WebServiceRef(wsdlLocation=
       "https://serverName:8181/HelloEjbService/HelloEjb?WSDL")
   private static HelloEjbService helloEjbService;
이어서 웹 서비스에 대한 포트에 액세스하여 웹 서비스를 호출할 수 있다.
   HelloEjb helloEjbPort = helloEjbService.getHelloEjbPort();
   helloEjbPort.hello("Hello World");
클라이언트 측 artifacts는 HTTPS를 통해 WSDL 파일에 액세스함으로써 생성되는데, 이를 위해서는 환경 변수 VMARGS 내에 서버와 해당 비밀번호를 위한 truststore 파일의 위치를 명시해야 한다. SSL에서는 인증서를 이용하여 서버를 인증하거나 서버 및 클라이언트 양자의 상호 인증을 수행할 수 있다. 서버의 truststore 파일에는 서버에 대한 신뢰할 수 있는 인증서와 키가 포함되어 있다.
서버의 truststore 파일과 그 비밀번호를 명시하기 위한 여러 가지 방법들이 있는데, 예를 들어 다음과 같이 ant 스크립트에서 이를 명시할 수도 있다.
   <exec executable="wsimport">
     <env key="VMARGS" 
         value="-Djavax.net.ssl.trustStore=${truststore.location} 
         -Djavax.net.ssl.trustStorePassword=${ssl.password}"/>
    <arg line="-keep -d ${client.build} 
    https://serverName:8181/HelloEjbService/HelloEjb?WSDL"/>
   </exec>
클라이언트 Truststore 및 Keystore 설정
서버를 위한 truststore 파일 외에 클라이언트를 위한 truststore 파일도 필요하다. 클라이언트는 truststore 내의 인증서들과 대조하여 서버 인증서를 검증하는데, SSL을 이용한 안전한 접속이 이루어지려면 클라이언트 측 truststore가 서버 인증서를 신뢰해야 한다. SSL 상호 인증을 위해서는 서버 측 truststore 역시 클라이언트 인증서를 신뢰해야 한다. 또한 서버 측 truststore를 수정할 경우에는 서버를 재시작해야 한다(이로써 새 인증서를 사용할 수 있게 된다). 생산 증명은 공동 인증 기관(CA, Certificate Authority)에서 승인하기 때문에 생산 환경에서는 상기 과정이 필요치 않을 수 있다. GlassFish 빌드에는 몇 가지의 공동 루트CA에 대한 인증서가 포함되어 있다.
SSL 상호 인증의 경우에는 클라이언트 keystore(클라이언트의 키와 인증서가 포함된 파일)에 자체 키를 제공해야 하고, keystore는 JDK 5.0에 포함된 키와 인증서 관리 유틸리티인 keytool을 이용하여 생성할 수 있다.
클라이언트 실행
SSL을 통해 웹 서비스에 액세스하는 클라이언트를 실행하기에 앞서 먼저 환경 변수 VMARGS의 값을 설정해야 한다. 그런 다음에야 평소처럼 애플리케이션을 실행할 수 있다. SSL의 경우에는 VMARGS의 값을 다음과 같이 설정한다.
   -Djavax.net.ssl.trustStore=${truststore.location} 
   -Djavax.net.ssl.trustStorePassword=${ssl.password}
SSL 상호 인증의 경우에는 VMARGS의 값을 다음과 같이 설정한다.
   -Djavax.net.ssl.trustStore=${truststore.location} 
   -Djavax.net.ssl.trustStorePassword=${ssl.password} 
   -Djavax.net.ssl.keyStore =${keystore.location} 
   -Djavax.net.ssl.keyStorePassword=${ssl.password}
예제 코드 실행하기
본 팁에 첨부된 예제 코드를 실행하려면 다음 단계를 수행한다.
  1. GlassFish를 아직 구하지 못했다면 GlassFish 커뮤니티 다운로드 페이지에서 다운로드하여 설치한다.
  2. 아래의 환경 변수를 설정한다:

    • GLASSFISH_HOME. GlassFish의 설치 장소를 표시해야 한다.
    • ANT_HOME. ant의 설치 장소를 표시해야 한다. Ant는 다운로드한 GlassFish 번들에 포함되어 있다. (Windows에서는 libant 서브디렉토리에 있음.)
    • JAVA_HOME. 사용자 시스템에서의 JDK 5.0 위치를 표시해야 한다.
    PATH 환경 변수에 $JAVA_HOME/bin, $ANT_HOME/bin$GLASSFISH_HOME/bin을 추가한다.
  3. 예제 패키지를 다운로드하여 압축을 푼다. 새로 압축이 풀린 디렉토리는 <sample_install_dir>/ttmay2006ws-ssl로 표시되어야 하는데, 이 때 <sample_install_dir>은 예제 패키지가 설치된 디렉토리를 나타낸다. ttmay2006ws-ssl 아래의 ws-ssl 디렉토리에는 예제용 소스 파일과 기타 지원 파일이 포함되어 있다.
  4. ws-ssl 디렉토리로 이동하여 build.properties 파일을 적절히 편집한다. 예를 들어 admin 호스트가 원격인 경우에는 admin.host의 값을 기본값localhost에서 해당 원격 호스트로 변경한다. 아울러, 아래의 속성들이 올바르게 명시되어 있는지 확인한다.

    • server.cert.cn. server.cert.cn. 서버 인증서의 CN 이름. 이는 호스트명 또는 호스트명과 도메인명을 합한 FQDN이어야 한다. GlassFish의 기본값 설치에서 서버 인증서에는 alias name s1as가 포함된다. 다음 명령어를 통해 CN을 디스플레이할 수 있다.
              keytool -list -v -alias s1as -keystore 
              $GLASSFISH_HOME/domains/domain1/config/cacerts.jks
      
      다음으로 keystore 비밀번호가 프롬프트되면 기본값 비밀번호 changeit를 입력한다. 이 때, 비밀번호는 변경이 가능하다.
    • admin.user. 도메인을 시작/중단하는 관리자의 ID.
    • admin.port. 관리 서버의 http 포트 번호.
    • https.port. 서버의 https 포트 번호.
  5. passwd 파일 내의 AS_ADMIN_PASSWORD 값을 관리자 비밀번호로 업데이트한다.
  6. 다음 명령어를 입력하여 클라이언트 키 및 인증서를 설정한다.
          ant setup
    
    이렇게 하면 로컬 keystore에 개인 키가 생성되고 인증서가 익스포트되어 이를 공통 truststore에 임포트하게 된다. 이때 자체 서명 인증서가 truststore에 설치된다는 점에 유의할 것. 이는 단지 테스트용일 뿐 실제 제작 상황에서 권장할 만한 사항은 아니다.
  7. GlassFish가 실행 중이면 아래와 같이 중단시킨다.
          $GLASSFISH_HOME/bin/asadmin stop-domain domain1
    
    그런 다음 작업을 시작한다.
       
          $GLASSFISH_HOME/bin/asadmin start-domain domain1
    
    이렇게 하면 truststore에서 새로운 인증서를 불러오게 된다.
  8. 예제를 구축하고 배포한다. 먼저 다음의 명령어를 입력한다.
          ant build
    
    이로써 EJB와 Servlet Web Services 클래스가 컴파일되고 ear 파일이 생성된다.

    이어서 아래 명령어를 입력한다.
          ant deploy
    
    이제 GlassFish에 ear 파일이 배포된다.

    마지막으로 아래 명령어를 입력한다.
          ant build-client
    
    이 명령어는 artifacts를 생성하고 클라이언트 측 코드를 컴파일하는데 사용된다.
  9. 클라이언트 애플리케이션을 실행하여 SSL을 통해 웹 서비스에 액세스하고, 다음 명령어를 입력한다.
          ant run
    
    아래와 유사한 결과가 표시되어야 한다.
          [exec] Retrieving port from the service 
          ejbws.HelloEjbService@4e21db
          [exec] Invoking hello operation on the 
          HelloEjbService port
          [exec] Ejb WS: CN=serverName, OU=SSLClient, O=EJTechTips, 
          L=Santa Clara, ST=California, C=US: Hello World
          [exec] Retrieving port from the service 
          servletws.HelloServletService@ea7549
          [exec] Invoking hello operation on the 
          HelloServletService port
          [exec] Servlet WS: CN=serverName, OU=SSLClient, 
          O=EJTechTips, L=Santa Clara, ST=California, 
          C=US: Hello World
    
    jvm 옵션-Djavax.net.debug=ssl,handshake를 추가하여 SSL handshake에 관한 정보를 비롯한 추가 정보를 볼 수 있다. 예제의 경우에는 아래 명령어를 입력하면 된다.
           ant run-debug
    
    handshake 과정에서 CertificateRequest를 살펴보면 이것이 SSL 상호 인증인지 확인할 수 있다.

    디버깅 정보를 더 보고 싶으면 jvm 옵션 -Djavax.net.debug=all을 이용한다.
  10. 아래 명령어를 입력하여 웹 서비스 ear 파일을 배포 해제할 수 있다.
           ant undeploy
    
  11. 애플리케이션을 배포 해제한 후에는 keystores에서 생성된 클라이언트 인증서를 제거하여 메모리에서 인증서가 제거되도록 한다. 이를 위해서는 다음 명령어를 실행한다:
           ant unsetup
           $GLASSFISH_HOME/bin/asadmin stop-domain domain1
           $GLASSFISH_HOME/bin/asadmin start-domain domain1 
    

댓글 없음:

댓글 쓰기