JCF3.0™ 웹서비스 적용 가이드

From JCFWiKi

Jump to: navigation, search

그림:check.gif

  • 산출물 : JCF3.0™ 웹서비스 적용 가이드
  • 작성자: 서경진
  • 작성일 : 2007/12/03
  • 버전 : 0.9
  • 개정이력 :

Copyright © 2007 Daewoo Information Systems Co., Ltd.

목차

[편집] CXF 아키텍처 이해하기

[편집] 개요

CXF는 서비스를 위해 필요한 다양한 infrastructrue component들을 구축하고자 한다. CXF는 다음과 같이 다양한 목표를 가지고 있다.

* Support for different bindings, including SOAP, REST, and Corba. -> 다양한 바인딩 기술을 지원
* WS-* support, including WS-Addressing, WS-Security, WS-ReliableMessaging, and WS-Policy -> WS-* 관련 기반기술 지원
* Support for multiple transports -> 다양한 자체 transport abstraction layer 지원
* Pluggable Data-bindings -> 이식성이 용이한 데이터 바인딩 지원
* Clean separation of front ends, like JAX-WS, from the core code. -> 코어 코드로부터 front ends의 명확한 분리
* High Performance -> 고성능 지원
* Embeddable -> 임베딩이 가능하도록 지원

[편집] CXF 아키텍처 구성요소

CXF는 다음과 같은 아키텍처 구성요소로 이루어져 있다.

1. Bus: CXF 아키텍처의 Backbone
2. Messaging & Interceptor: 대부분의 기능이 구축되는 low level message와 pipeline layer를 제공
3. Frontends: Frontends는 서비스들을 제공하는 프로그래밍 모델을 제공
4. Services: Services는 서비스를 기술하는 WSDL과 같은 모델인 서비스 모델을 제공
5. Bindings: Bindings는 프로토콜을 해석하는(interpret) 기능을 제공
6. Transports: Destinations과 Conduits는 CXF가 transport neutrality를 지원하기 위해 사용하는 transport abstraction을 구성한다.
   (Destinations and Conduits make up the transport abstraction that CXF uses to achieve transport neutrality.)

이제 각 구성요소들에 대해 자세하게 살펴보고, 각 구성요소들이 어떻게 상호연동하는지를 보도록 하겠다.

  • Bus

Bus는 Runtime에 공유자원을 제공한다. 예를 들어, wsdl managers, binding factory managers과 같은 공유자원들이 있다. bus는 사용자 정의 자원이나 서비스들을 포함하도록 쉽게 확장될 수 있다. 또는 개발자가 the HTTP destination factory와 같이 디폴트 자원으로 대체할 수 있다. 디폴트 bus의 구현이 runtime component들을 상호연계하는 Spring에 기반으로 이루어졌기 때문에, 위와 같은 자원이나 서비스의 할당이 dependency injection에 의해 구성될 수 있다. SpringBusFactory는 classpath에서 META-INF/cxf에 있는 모든 bean 설정파일을 찾는다, 그리고 설정파일로부터 application context를 구성한다. application context 생성에 포함된 bean 설정파일은 다음과 같다.

META-INF/cxf/cxf.xml (in cxf-rt-core only)
META-INF/cxf/cxf-extension.xml (e.g. in cxf-rt-bindings-soap)
META-INF/cxf/cxf-property-editors.xml (e.g. in cxf-rt-transports-http).

사용자가 정의한 bean 설정파일을 제공하여 bus를 재정의할 수 있다. 예를 들어, factory method를 사용하여 다음과 같이 자신의 bean 설정파일을 가지고 bus를 생성할 수 있다.

SpringBeanFactory.createBus("mycxf.xml")

개발자는 CXF가 디폴트로 사용하던 bean 정의를 수정할 수 있다. 이 경우에, cxf.xml은 classpath에 위치해야 한다. 그리고 bean 설정파일을 지시하는 URL을 가져가는 factory method를 사용할 수 있다.
예를 들어 cxf.xml에 정의된 bus bean을 사용자 정의 설정파일에 정의된 bean으로 다음과 같이 대체할 수 있다.

<bean id="logInbound" class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
<bean id="logOutbound" class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
 
<bean id="cxf" class="org.apache.cxf.bus.CXFBusImpl">
    <property name="inInterceptors">
        <list>
            <ref bean="logInbound"/>
        </list>
    </property>
    <property name="outInterceptors">
        <list>
            <ref bean="logOutbound"/>
        </list>
    </property>
    <property name="outFaultInterceptors">
        <list>
            <ref bean="logOutbound"/>
        </list>
    </property>
</bean>
...

위에서 생성된 bus는 사용자 정의 LoggingInterceptor로 구성된 out interceptors의 리스트를 사용할 것이다.(the default list of out interceptors at the bus level is empty)

  • Messaging & Interceptors

CXF는 Messages, Interceptors 그리고 InterceptorChains로 구성된 generic messaging layer에서 구축된다. Interceptors는 기능구현을 위한 기초적인 유닛이다. messages가 처리되고 보내지는 방법을 구분하는 interceptors의 기능으로 인해 CXF는 유연한 아키텍처를 구성하게 된다. Messages를 처리하는 과정의 특정 지점에서 intercepter에 의해 재설정될 수 있다. 또한 interceptor는 CXF에서 interceptor chains을 일시정지하고 복구하는 기능을 부여한다. Interceptors는 Message에 행위를 하도록 허가하는 method, handleMessage를 가지고 있다. 그 다음에 이런 Interceptors는 InterceptorChains로 직접적으로 호출될 수 있는 interceptor chains으로 구성될 수 있다. 다음은 Messaging & Interceptors에 대한 예시이다.

* DOM element 단위로 SOAP 메시지의 헤더를 파싱하는 interceptor (An interceptor which parses just the headers of a SOAP message into DOM elements)
* 들어오는 메시지에 대한 복호화나 인증을 담당하는 WS-Security interceptor (A WS-Security interceptor which decrypts or authenticates an incoming message)
* 결과와 직력화시키는 유출되는 데이터에 대한 binding interceptor (An outgoing data binding interceptor which serializes the result)
  • Databinding

CXF는 디폴트 databinding으로 JAXB 2.x를 사용한다. 또한 그 밖에 2가지의 다른 databinding을 지원한다. Map, List 등을 포함하여 자바 오브젝트를 schema를 사용하여 표현하여 직렬화가 가능하도록 지원하는 Aegis Databinding과 소스(Source) 오브젝트를 읽고 쓸 수 있도록 지원하는 SourceDataBinding이 있다.

* Aegis Databinding
* JAXB
* MTOM Attachments: SourceDatabinding
  • WS-* Support

WS-Security

WS-Security는 HTTPS와 같이 트랜스포트(transport) 수준의 프로토콜상에서 서비스를 안전하게 보호하는 방법을 제공한다. XML암호화와 WS-Security에 정의된 헤더와 같은 수많은 표준을 통해 다음과 같은 보안 기능을 제공한다.

* 서비스간 인증토큰 전달 (Pass authentication tokens between services)
* 전체 및 부분 메시지의 암호화 (Encrypt messages or parts of messages)
* 메시지 전자서명 (Sign messages)
* 메시지 타임스탬프 (Timestamp messages)

현재 CXF는 Apache WSS4J를 사용하여 WS-Security Integration을 구현하였다. WS-Security Integration을 사용하기 위해서 Interceptor를 설정하고 서비스와 클라이언트에 추가하는 것이 필요하다.

WS-ReliableMessaging

CXF는 2005년 2월 웹서비스 버전부터 Web Services Reliable Messaging Protocol(WS-ReliableMessaging)을 지원한다. CXF에 있는 대부분의 다른 특성들처럼, interceptor를 기반으로 WS-ReliableMessaging이 제공된다. WS-Reliable Messaging은 구현된 총 4개의 interceptor로 구성된다.

QOS(Quality Of Service)를 위한 Interceptor

interceptor chain들 중에서 RM interceptor 홀로 필요할 때 RM 프로토콜 메시지를 교환하는데 관여한다. 예를 들어, outbound interceptor chain에서 첫 번째 어플리케이션 메시지를 intercept한다면, RMOutInterceptor는 CreateSequence 요청을 보내고 바로 CreateSequenceResponse로 요청에 응답한 이후에 원본 어플리케이션 메시지에 대한 처리를 진행한다. 그러므로 RM interceptor는 어플리케이션 메시지에 Sequence headers를 추가하고 목적지에서는 메시지로부터 Sequence headers를 추출하는 역할을 담당한다.

이러한 RM intercepter의 적용을 통해 어플리케이션 코드의 변경 없이 메시지의 전송이 안정적으로 이루어지도록 지원한다.

효과적인 RM 프로토콜을 지원하기 위해, 설정을 통한 안정적 메시지 교환의 방법으로 sequene를 제한하는 통제방법이 있다. 예를 들어, CXF는 디폴트로 sequence의 생명주기를 최대화하려고 시도하는 동안, RM 프로토콜 메시지에 의해 발생하는 오버헤드가 증가하는데 이것을 감소시키고자 한다면, RM source's sequence termination 정책(최대 sequence 길이를 1로 설정)을 적용하여 어플리케이션 메시지 단위로 분리된 sequence를 사용하도록 강제로 정의할 수 있다.

[편집] Spring 기반 CXF 적용하기

[편집] CXF 웹서비스를 위한 필수 라이브러리

  • Server/Client 필수 라이브러리

CXF 기반의 웹서비스가 정상적으로 구동하기 위해 필요한 필수 라이브러리이다. 참고로 applicationContext-webservice.xml에서 <import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml"/>를 제거하면 jetty와 관련된 라이브러리를 제거해도 정상적으로 동작한다. 하지만 이 가이드에서는 CXF 웹사이트에서 제공하는 가이드를 기반으로 구성하였으므로 jetty를 제거하지 않는다.

1. 필수 라이브러리
asm-all-2.1.jar
bcprov-jdk15-138.jar
commons-logging.jar
cxf-2.0.3-incubator.jar
cxf-manifest-incubator.jar
geronimo-activation_1.1_spec-1.0-M1.jar
geronimo-annotation_1.0_spec-1.1.jar
geronimo-javamail_1.4_spec-1.0-M1.jar
geronimo-servlet_2.5_spec-1.1-M1.jar
geronimo-ws-metadata_2.0_spec-1.1.1.jar
jaxb-api-2.0.jar
jaxb-impl-2.0.5.jar
jaxen-1.1.jar
jaxp-ri.jar
jaxrpc.jar
jaxws-api-2.0.jar
jcf.jar
jdom-1.0.jar
junit-4.0.jar
neethi-2.0.jar
saaj-api-1.3.jar
saaj-impl-1.3.jar
spring.jar
stax-api-1.0.1.jar
stax-utils-20060502.jar
wsdl4j-1.6.1.jar
wss4j-1.5.1.jar
wstx-asl-3.2.1.jar
xalan-2.7.0.jar
xbean-2.2.0.jar
xercesImpl.jar
xml-apis-1.3.02.jar
xml-resolver-1.2.jar
XmlSchema-1.3.2.jar
xmlsec-1.3.0.jar

2. jetty 사용 시 추가 라이브러리 (services.xml에서 include 필요)
jettison-1.0-RC2.jar
jetty-6.1.3.jar
jetty-util-6.1.3.jar

[편집] 아키텍처 구조

  • FrontEnd: Simple FrontEnd

FrontEnd는 서비스들을 제공하는 Spring 기반의 프로그래밍 모델을 제공한다. CXF는 Simple는 JAX-WS 두 가지의 FrontEnd를 제공한다. 이 가이드라인에서는 annotation 기반으로 적용되지 않는 Simple FrontEnd를 채택하고 아키텍처를 구성하고자 한다.

  • DataBinding: AegisDatabinding

CXF는 디폴트 DataBinding으로 JAXB 2.X를 사용한다. 또한 CXF는 다른 두 개의 Databinding을 지원한다. 그 중 하나인 Aegis Databinging은 어떤 자바 객체든지 Map, List, 그리고 annotation을 사용하여 표현되지 않은 자바형을 포함하면서 스키마를 사용해서 표현될 수 있는 것으로 변환한다. 또 다른 하나로 SourceDataBinding인데, 이 databinding은 Source 객체를 읽고 쓰는 것을 지원한다. 여기에서는 CXF의 이전 버전인 XFire에서부터 지원되던 AegisDatabinding을 사용할 것이다. AegisDatabing은 다음 데이터형을 지원한다.

* Basic types: int, double, float, long, byte[], short, String, BigDecimal
* Arrays
* Collections - including Maps
* Dates: java.util.Date, java.util.Calendar, java.sql.Timestamp, java.sql.Date, java.sql.Time
* XML: org.w3c.dom.Docmument, org.jdom.Element, XMLStreamReader, Source
* Complex types which are aggregations of the above

하지만 Collection이나 Map 그리고 사용자가 정의한 데이터형의 경우는 [인터페이스명.aegis.xml] 파일에 지정된 스키마를 기반으로 각 method, component, proterty에 따라 parameter나 retern-type과 같은 속성값을 지정해 주는 것이 필요하다. 다음은 AegisDatabinding을 위해 구성된 설정파일의 예제이다.

BookService.aegis.xml

<mappings>
    <mapping>
        <method name="findBook">
            <parameter index="0" mappedName="isbn"/>
            <return-type mappedName="book" />
        </method>
         <method name="getBooksMap">
            <parameter index="0" mappedName="param"/>
            <return-type mappedName="BookMap" keyType="java.lang.String" componentType="demo.book.Book" />
        </method>
   </mapping>
</mappings>

위의 설정파일은 BookService에 정의된 findBook 메소드에 대하여 AegisBinding을 위해 0번째 parameter를 isbn이라 명명하고 return-type을 book이라고 명명한 것이다. 또 다른 getBookMap 메소드에 대하여 AegisBinding을 위해 0번째 parameter를 param이라 명명하고 return-type을 BookMap이라고 명명할 뿐만아니라 Map형의 Databinding을 위해 Map의 key를 구성하는 keyType과 value를 구성하는 componentType을 지정한다. 다음 스키마 파일은 aegis.xml 설정파일을 스키마에 대하여 설명한 것이다.

aegis.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  elementFormDefault="unqualified" attributeFormDefault="unqualified"
>
<!-- top level element: mappings -->
  <xsd:element name="mappings">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="mapping" type="mappingType" minOccurs="0" maxOccurs="unbounded" />
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
<!-- each mapping is one of these. -->
  <xsd:complexType name="mappingType">
    <xsd:choice minOccurs="0" maxOccurs="unbounded">
    <!-- a property from a bean, or -->
      <xsd:element name="property" type="propertyType" />
    <!-- a method for a service, or -->
      <xsd:element name="method" type="methodType" />
    <!-- an extra type for use with an untyped collection -->
      <xsd:element name="component" type="componentTypeType" />
    </xsd:choice>
    <!-- if you specify a uri, it binds the type to a specific service by namespace. This could be the soap 1.1 namespace. -->
    <xsd:attribute name="uri" type="xsd:string" />
    <!-- name of the type -->
    <xsd:attribute name="name" type="xsd:string" />
  </xsd:complexType>
  
  <!-- a property. see the attributes. -->
  <xsd:complexType name="propertyType">
    <xsd:attributeGroup ref="mappedType" />
    <xsd:anyAttribute namespace='##other' processContents='lax' />
  </xsd:complexType>
 
  <!-- a method. One name, plus parameters for disambiguation (and type binding), plus a return-type if any. -->
  <xsd:complexType name="methodType">
    <xsd:choice minOccurs="0" maxOccurs="unbounded">
      <xsd:element name="return-type" type="return-typeType" />
      <xsd:element name="parameter" type="parameterType" />
    </xsd:choice>
    <xsd:attribute name="name" type="xsd:string" />
  </xsd:complexType>
 
  <xsd:complexType name="return-typeType">
    <xsd:attributeGroup ref="mappedType" />
    <xsd:anyAttribute namespace='##other' processContents='lax' />
  </xsd:complexType>
 
  <xsd:complexType name="parameterType">
    <xsd:attribute name="index" type="xsd:int" />
    <xsd:attribute name="class" type="xsd:string" />
    <xsd:attributeGroup ref="mappedType" />
    <xsd:anyAttribute namespace='##other' processContents='lax' />
  </xsd:complexType>
 
  <xsd:complexType name="componentTypeType">
    <xsd:attribute name="class" type="xsd:string" />
    <xsd:attributeGroup ref="mappedType" />
    <xsd:anyAttribute namespace='##other' processContents='lax' />
  </xsd:complexType>
 
  <xsd:attributeGroup name="mappedType">
    <xsd:attribute name="name" type="xsd:string" />
    <xsd:attribute name="type" type="xsd:string" />
    <xsd:attribute name="typeName" type="xsd:string" />
    <xsd:attribute name="mappedName" type="xsd:string" />
    <xsd:attribute name="nillable" type="xsd:boolean" />
    <xsd:attribute name="flag" type="xsd:boolean" />
    <xsd:attribute name="ignore" type="xsd:boolean" />
    <xsd:attribute name="componentType" type="xsd:string" />
    <xsd:attribute name="keyType" type="xsd:string" />
    <xsd:attribute name="valueType" type="xsd:string" />
    <xsd:attribute name="minOccurs" type="xsd:int" />
    <xsd:attribute name="maxOccurs" type="xsd:string" />
    <xsd:attribute name="style">
      <xsd:simpleType>
        <xsd:restriction base="xsd:string">
          <xsd:enumeration value="attribute" />
          <xsd:enumeration value="element" />
        </xsd:restriction>
      </xsd:simpleType>
    </xsd:attribute>
    <xsd:anyAttribute namespace='##other' processContents='lax' />
  </xsd:attributeGroup>
</xsd:schema>
  • CXF WebService Servlet Configuration

HTTP Request 기반의 SOAP 메시지를 받기 위해 WAS의 설정파일(Tomcat, WebSphere의 경우 web.xml)에 다음과 같은 설정을 추가한다.

...
<servlet>
   <servlet-name>CXFServlet</servlet-name>
   <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
   <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
   <servlet-name>CXFServlet</servlet-name>
   <url-pattern>/services/*</url-pattern>
</servlet-mapping>
...
<context-param>
   <param-name>contextConfigLocation</param-name> 
   <param-value>/WEB-INF/classes/applicationContext*.xml</param-value>
</context-param>
...

이와 같이 설정하는 경우 어플리케이션 컨텍스트 밑에 /services/로 들어오는 요청은 CXFServlet이 처리하게 되는 것이다.

  • ServiceModel Configuration

Spring 기반의 서비스에 대하여 FrontEnd를 통해 ServiceModel을 구성하기 위해 Spring의 ApplicationContext에 Simple FrontEnd가 구동하기 위한 설정이 필요하다. 위의 web.xml에 <context-parm>의 설정으로 applicationContext-webservice.xml가 로딩되는 것을 확인할 수 있다. 다음 예제는 applicationContext-webservice.xml에 BookService(인터페이스)를 Simple FrontEnd 웹서비스로 설정하는 것이다.

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:simple="http://cxf.apache.org/simple"
	xmlns:soap="http://cxf.apache.org/bindings/soap"
	xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd
http://cxf.apache.org/simple http://cxf.apache.org/schemas/simple.xsd">
 
<!-- import resource를 통해 해당 설정이 사용하는 라이브러리를 참조하도록 지정한다.-->
<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-http.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-http-binding.xml"/>
<!-- <import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml" jetty 사용 시 추가 필요/-->
<import resource="classpath:META-INF/cxf/cxf-extension-local.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-xml.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-object-binding.xml"/>
<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
 
   <bean id="logInbound" class="org.apache.cxf.interceptor.LoggingInInterceptor"/> <!-- Inbound 메시지를 로깅하여 콘솔에 출력한다.-->
   <bean id="logOutbound" class="org.apache.cxf.interceptor.LoggingOutInterceptor"/> <!-- Outbound 메시지를 로깅하여 콘솔에 출력한다.-->
 
   <bean id="cxf" class="org.apache.cxf.bus.CXFBusImpl"> <!-- Bus를 통해Inbound/Outbound 메시지에 대한 로깅을 관리하도록 설정한다.-->
      <property name="inInterceptors">
         <list>
            <ref bean="logInbound"/>
         </list>
      </property>
      <property name="outInterceptors">
         <list>
            <ref bean="logOutbound"/>
         </list>
      </property>
      <property name="outFaultInterceptors">
         <list>
            <ref bean="logOutbound"/>
         </list>
      </property>
   </bean>
 
   <bean id="aegisBean" class="org.apache.cxf.aegis.databinding.AegisDatabinding"/> <!-- AegisDatabinding을 Spring Bean으로 등록한다.-->
 
   <simple:server id="bookWebService" serviceClass="demo.spring.book.BookService" address="/BookService"> <!-- 웹서비스를 위한 FrontEnd를 지정한다.-->
      <simple:dataBinding>
         <ref bean="aegisBean"/> <!-- 해당 웹서비스가 사용하는 Databinding을 AegisDatabinding으로 설정한다.-->
      </simple:dataBinding>
      <simple:serviceBean> <!-- BookService의 구현클래스가 Spring Bean으로 등록되어 있다면 bean id를 설정한다.-->
         <ref bean="bookService"/>
      </simple:serviceBean>
   </simple:server>
</beans>
  • Client Implementation

공개된 CXF 웹서비스를 클라이언트가 요청하여 서비스를 받기 위해서는 다음과 같이 구성한다.

public class WebServiceClient {
 
	private static BookService bookServiceClient;
 
	public static void main(String args[]) throws Exception {
		setUpService();
		......
	}
	......
	private static void setUpService() throws Exception {
	   ClientProxyFactoryBean factory = new ClientProxyFactoryBean();
	   factory.setServiceClass(BookService.class);
	   factory.setAddress("http://localhost:8080/book/services/BookService");
	   factory.getServiceFactory().setDataBinding(new AegisDatabinding());
	   bookServiceClient = (BookService) factory.create();
	}
	......
}
  • WSDL Bindings: MTOM Attachments

Spring 기반의 서버에서 MTOM Attachments를 적용하기 위해서는 applicationContext-webservice.xml의 다음 부분에 강조된 properties를 추가하면 해당 Databinding 기능을 사용할 수 있다.

   <simple:server id="bookWebService" serviceClass="demo.spring.book.BookService" address="/BookService">
      <simple:dataBinding>
         <ref bean="aegisBean"/>
      </simple:dataBinding>
      <simple:serviceBean>
         <ref bean="bookService"/>
      </simple:serviceBean>
      <simple:properties>
         <entry key="mtom-enabled" value="true"/> <!-- MTOM Attachments를 사용하기 위해 mtom-enabled 키값을 true로 설정한다.-->
      </simple:properties>
</simple:server>

상대적으로 클라이언트에서 MTOM Attachments를 사용하기 위해서는 클라이언트를 구성하는 클래스에 다음과 같은 함수를 구현한다. 다음은 클라이언트를 자바 어플리케이션 형태로 구성한 예제이다.

public class WebServiceClient {
 
	private static BookService bookServiceClient;
 
	public static void main(String args[]) throws Exception {
		setUpService();
		......
	}
	......
	private static void setUpService() throws Exception {
Map<String, Object> props = new HashMap<String, Object>(); <!-- java5 이상에서 GenericType을 지원한다. -->
props.put("mtom-enabled", Boolean.TRUE);
 

ClientProxyFactoryBean factory = new ClientProxyFactoryBean(); factory.setServiceClass(BookService.class); factory.setProperties(props); factory.setAddress("http://localhost:8080/book/services/BookService"); factory.getServiceFactory().setDataBinding(new AegisDatabinding()); bookServiceClient = (BookService) factory.create(); } ......

}

예제를 기반으로 서버와 클라이어트를 구성하여 웹서비스를 테스트할 수 있다.

  • WS-* Support: WS-Security, WS-ReliableMessaging

Spring 기반의 서버에서 WS-Security를 적용하여 웹서비스를 제공하기 위해서는 다음과 같이 설정한다.

<simple:server id="bookWebService"
	serviceClass="demo.spring.book.BookService" address="/BookService">
	<simple:serviceBean>
                <!-- 구현클래스가 Spring Bean으로 등록되지 않음 경우 -->
		<bean class="demo.spring.book.BookServiceImpl"/>
		<!-- <ref bean="bookService"/> 구현클래스가 Spring Bean으로 등록된 경우 -->
	</simple:serviceBean>
	<simple:inInterceptors>
                <!-- WS-Security를 위한 inbound SAAJInInterceptor를 설정한다. -->
		<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor"/>
                <!-- WS-Security를 위한 inbound WSS4JInInterceptor를 설정한다. -->
		<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"> 
			<constructor-arg>
				<map>
                                        <!-- interceptor의 action이 Encrypt와 Signature임을 설정한다.-->
					<entry key="action" value="Encrypt Signature"/>
                                        <!-- action을 수행하기 위해 참조하는 Signature 프로퍼티 파일을 설정한다.-->
					<entry key="signaturePropFile" value="META-INF/cxf/insecurity.properties"/>
                                        <!-- action을 수행하기 위해 참조하는 decryption 프로퍼티 파일을 설정한다.-->
					<entry key="decryptionPropFile" value="META-INF/cxf/insecurity.properties"/>
					<!-- Password Callback 클래스의 설정으로 WS-Security가 주어진 사용자들의 인가여부를 결정하기 -->
                                        <!-- 위하여 Callback 클래스가 주어진 사용자들에 대한 패스워드를 검색하기 위한 검색을 하가하게 된다. -->
					<entry key="passwordCallbackClass" value="org.apache.cxf.ws.security.wss4j.TestPwdCallback"/>
				</map>
			</constructor-arg>
		</bean>
		<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor"/>
	</simple:inInterceptors>
	<simple:outInterceptors>
		<bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor"/>
		<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
			<constructor-arg>
				<map>
					<entry key="action" value="Encrypt Signature"/>
					<entry key="signaturePropFile" value="META-INF/cxf/outsecurity.properties"/>
					<entry key="encryptionPropFile" value="META-INF/cxf/outsecurity.properties"/>
					<entry key="user" value="myalias"/>
					<entry key="password" value="myAliasPassword"/>
					<entry key="passwordCallbackClass" value="org.apache.cxf.ws.security.wss4j.TestPwdCallback"/>
				</map>
			</constructor-arg>
		</bean>
		<bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor"/>
	</simple:outInterceptors>
</simple:server>

WS-Security를 사용하기 위해 SAAJ(In/Out)Interceptor와 WSS4J(In/Out)Interceptor를 추가한다. 그리고 applicationContext-webservice.xml에 WSS4JInterceptor의 Action과 같은 entry에 대한 값을 설정한다.

WS-Security는 token을 사용하는 다양한 방법을 제공한다. 다음은 xml이 아닌 자바코드에서 Action을 설정하고 사용자명과 패스워드 또는 패스워드 다이제스트를 통신하는 기본적인 방법을 나타낸다. 우선, 서버에서 WSS4JInInterceptor에 다음 프로퍼티를 설정할 것이다.

inProps.setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
// Password type : plain text
inProps.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// for hashed password use:
//properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
// Callback used to retrive password for given user.
inProps.setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, ServerPasswordHandler.class.getName());

WS-Security가 주어진 사용자들의 인가 여부를 결정하기 위하여 Callback 클래스가 주어진 사용자들에 대한 패스워드를 검색하기 위한 검색을 하가하게 된다. 다음은 Password Callback 클래스의 예제이다.

public class ServerPasswordCallback implements CallbackHandler {
 
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
 
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
 
        if (pc.getId().equals("joe") {
            // set the password on the callback. This will be compared to the
            // password which was sent from the client.
            pc.setPassword("password");
        }
    }
 
}

클라이언트에서도 마찬가지로 WSS4J outgoing 프로퍼티를 다음과 같이 설정한다.

outProps.setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
// Specify our username
outProps.setProperty(WSHandlerConstants.USER, "joe");
// Password type : plain text
outProps.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// for hashed password use:
//properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
// Callback used to retrive password for given user.
outProps.setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, ClientPasswordHandler.class.getName());

클라이언트에서도 다시 한 번 Password Callback 클래스를 사용하는데, 서버와는 다르게 메시지와 함께 설정된 패스워드를 서버에 보낸다. 서버에서는 위에서 설정된 사용자를 기반으로 클라이언트에서 보낸 패스워드를 Callback 클래스를 통해 비교하여 인가여부를 결정한다. 다음은 클라이언트 Callback 클래스의 예제이다.

public class ClientPasswordCallback implements CallbackHandler {
 
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
 
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
 
        // set the password for our message.
        pc.setPassword("password");
    }
 
}
  • Advanced Integration

[편집] CXF Sample 테스트하기

junit4를 활용하여 CXF Sample TestCase를 구성하고 Simple FrontEnd, AegisDatabinding, WS-Security에 대한 테스트를 수행한다.

[편집] CXF Sample 구조

CXF Sample은 Simple FrontEnd로 다양한 데이터형에 대한 AegisDatabinding과 WS-Security의 정상 작동 여부를 판단하기 위해서 HelloCxf와 Employee 인터페애스와 TestModel 클래스를 정의한다.

HelloCxf.java

package demo.spring;
 
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.activation.DataHandler;
 
public interface HelloCxf {
 
	String sayHi(String text);
 
	public DataHandler getMtom();
 
	public void setMtom(DataHandler mtom);
	
	public void setMap(HashMap map);
	
	public HashMap getMap();
	
	public void setStringMap(String key, String value);
	
	public void setList(List<TestModel> list);
	
	public List<TestModel> getList();
	
	public TestModel getModel();
	
	public void setModel(TestModel model);
	
	public void setModelData(String id, String name, int age);
}

HelloCxf의 구현 클래스인 HelloCxfImpl은 다음과 같이 구현되어 있다. HelloCxfImpl.java

package demo.spring;
 
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
 
import javax.activation.DataHandler;
 
public class HelloCxfImpl implements HelloCxf {
 
	private DataHandler mtom;
 
	private HashMap map;
 
	private List<TestModel> list;
 
	private TestModel model;
 
	public TestModel getModel() {
		return model;
	}
 
	public void setModel(TestModel model) {
		this.model = new TestModel();
		this.model = model;
		System.out.println(this.model.getId());
		System.out.println(this.model.getName());
		System.out.println(this.model.getAge());
	}
 
	public void setModelData(String id, String name, int age) {
		this.model = new TestModel();
		this.model.setId(id);
		this.model.setName(name);
		this.model.setAge(age);
		System.out.println(this.model.getId());
		System.out.println(this.model.getName());
		System.out.println(this.model.getAge());
	}
 
	public HashMap getMap() {
		return map;
	}
 
	public void setMap(HashMap map) {
		this.map = new HashMap();
		this.map = map;
	}
 
	public void setStringMap(String key, String value) {
		this.map = new HashMap();
		this.map.put(key, value);
		System.out.println("HashMap 입력됨");
	}
 
	public DataHandler getMtom() {
		System.out.println("MTOM Server Getting Successful: " + this.mtom.getDataSource().getContentType());
		System.out.println("MTOM Server Getting Successful: " + this.mtom.getDataSource().getName());
		return this.mtom;
	}
 
	public void setMtom(DataHandler mtom) {
		this.mtom = mtom;
		System.out.println("MTOM Server Setting Successful: " + this.mtom.getDataSource().getContentType());
		System.out.println("MTOM Server Setting Successful: " + this.mtom.getDataSource().getName());
	}
 
	public String sayHi(String text) {
		return "Hello " + text;
	}
 
	public List<TestModel> getList() {
		return list;
	}
 
	public void setList(List<TestModel> list) {
		this.list = new ArrayList();
		this.list = list;
		for (Iterator iter = list.iterator(); iter.hasNext();) {
			TestModel model = (TestModel) iter.next();
			this.model.setId(model.getId());
			this.model.setName(model.getName());
			this.model.setAge(model.getAge());
			System.out.println(this.model.getId());
			System.out.println(this.model.getName());
			System.out.println(this.model.getAge());
		}
	}
 
}

HelloCxf에 AegisDatabinding을 적용하기 위한 HelloCxf.aegis.xml 파일은 다음과 같이 정의되어 있다. HelloCxf.aegis.xml

<?xml version="1.0" encoding="utf-8"?>
<mappings>
	<mapping>
        <method name="sayHi">
            <return-type componentType="java.lang.String"/>
			<parameter index="0" mappedName="text"/>
        </method>
		<method name="getMtom">
            <return-type componentType="javax.activation.DataHandler"/>
        </method>
		<method name="setMtom">
			<parameter index="0" mappedName="mtom"/>
        </method>
        <method name="setMap">
			<parameter index="0" mappedName="map" keyType='java.lang.String' valueType='java.lang.String'/>
        </method>
        <method name="setStringMap">
			<parameter index="0" mappedName="key"/>
			<parameter index="1" mappedName="value"/>
        </method>
        <method name="getMap">
            <return-type keyType='java.lang.String' valueType='java.lang.String'/>
        </method>
        <method name="getModel">
            <return-type componentType="demo.spring.TestModel"/>
        </method>
        <method name="setModel">
            <parameter index="0" mappedName="model" componentType="demo.spring.TestModel"/>
        </method>
        <method name="setModelData">
			<parameter index="0" mappedName="id"/>
			<parameter index="1" mappedName="name"/>
			<parameter index="2" mappedName="age"/>
        </method>
        <method name="getList">
            <return-type name="ModelList" componentType="demo.spring.TestModel"/>
        </method>
        <method name="setList">
        	<parameter index="0" mappedName="list" componentType="demo.spring.TestModel"/>
        </method>
    </mapping>
</mappings>

Employee.java

package demo.spring;
 
import javax.activation.DataHandler;
 
public interface Employee {
 
	public abstract String getName();
 
	public abstract void setName(String name);
 
	public abstract String getTitle();
 
	public abstract void setTitle(String title);
 
	public abstract DataHandler getMtom();
 
	public abstract void setMtom(DataHandler mtom);
 
	public abstract void setProperty(String name, String title);
 
	public abstract String getProperty();
}

EmployeeImpl.java

package demo.spring;
 
import javax.activation.DataHandler;
 
public class EmployeeImpl implements Employee {
 
	private String name;
 
	private String title;
 
	private DataHandler mtom;
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getTitle() {
		return title;
	}
 
	public void setTitle(String title) {
		this.title = title;
	}
 
	public String getProperty() {
		return "name: " + this.name + " title: " + this.title;
	}
 
	public void setProperty(String name, String title) {
		this.name = name;
		this.title = title;
	}
 
	public DataHandler getMtom() {
		return this.mtom;
	}
 
	public void setMtom(DataHandler mtom) {
		this.mtom = mtom;
	}
 
}

[편집] WS-Security 설정파일 구성

Employee 클래스에 대한 WS-Security가 정상적으로 구동하게 설정하기 위해 Callback 클래스와 프로퍼티 파일을 다음과 같이 정의한다.

TestPwdCallback.java

package org.apache.cxf.ws.security.wss4j;
 
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
 
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
 
import org.apache.ws.security.WSPasswordCallback;
 
public class TestPwdCallback implements CallbackHandler {
 
    private static Map<String, String> passwords = new HashMap<String, String>();
 
    static {
        passwords.put("myalias", "myAliasPassword");
    }
 
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
            WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
 
            String pass = (String)passwords.get(pc.getIdentifer());
            if (pass != null) {
                pc.setPassword(pass);
            }
        }
    }
}

WS-Security를 위한 프로퍼티 파일은 위 그림의 위치에 정의한다.그리고 keytool을 통해 key.rsa, privatestore.jks, publicstore.jks를 생성한다.

insecurity.properties

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=keyStorePassword
org.apache.ws.security.crypto.merlin.alias.password=myAliasPassword
org.apache.ws.security.crypto.merlin.keystore.alias=myalias
org.apache.ws.security.crypto.merlin.file=META-INF/cxf/privatestore.jks


outsecurity.properties

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=keyStorePassword
org.apache.ws.security.crypto.merlin.alias.password=myAliasPassword
org.apache.ws.security.crypto.merlin.keystore.alias=myalias
org.apache.ws.security.crypto.merlin.file=META-INF/cxf/privatestore.jks
xfire.security.symmetric.key.algoritm=http://www.w3.org/2001/04/xmlenc#tripledes-cbc
xfire.security.encrypt.key.algoritm= http://www.w3.org/2001/04/xmlenc#rsa-1_5

위의 모든 파일이 준비되면 TestCase를 구성할 수 있는 기반이 마련된 것이다.

[편집] CXF Sample TestCase 이해하기

Simple FrontEnd 기반의 TestCase는 AegisDatabinding과 WS-Security의 정상 작동여부를 확인할 수 있는 테스트로 다음과 같이 구성된다.

SimpleFrontEndTests.java

package demo.spring.client;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import junit.framework.TestCase;
 
import org.apache.cxf.aegis.databinding.AegisDatabinding;
import org.apache.cxf.aegis.type.Configuration;
import org.apache.cxf.aegis.type.DefaultTypeMappingRegistry;
import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor;
import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.frontend.ClientProxyFactoryBean;
import org.apache.cxf.frontend.ServerFactoryBean;
import org.apache.cxf.service.Service;
import org.apache.cxf.ws.security.wss4j.TestPwdCallback;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.junit.Before;
 
import demo.spring.Employee;
import demo.spring.EmployeeImpl;
import demo.spring.HelloCxf;
import demo.spring.HelloCxfImpl;
import demo.spring.TestModel;
 
public class SimpleFrontEndTests extends TestCase {
 
	private WSS4JInInterceptor wsIn;
 
	private WSS4JOutInterceptor wsOut;
 
	private HelloCxf helloCxf;
 
	private HelloCxf client;
 
	private Employee employee;
 
	private Employee empClient;
 
	private Client clientProxy;
 
	@Before
	public void setUpService() throws Exception {
		// Create our service implementation
		helloCxf = new HelloCxfImpl();
		employee = new EmployeeImpl();
 
		// Create our Server
		ServerFactoryBean svrFactory = new ServerFactoryBean();
		svrFactory.setServiceClass(HelloCxf.class);
		svrFactory.setAddress("http://localhost:8080/cxf/hello_cxf");
		svrFactory.setServiceBean(helloCxf);
		AegisDatabinding db = new AegisDatabinding();
		svrFactory.getServiceFactory().setDataBinding(db);
 
		DefaultTypeMappingRegistry tmr = (DefaultTypeMappingRegistry) db.getTypeMappingRegistry();
		Configuration configuration = tmr.getConfiguration();
		configuration.setDefaultMinOccurs(1);
		configuration.setDefaultNillable(false);
 
		svrFactory.create();
 
		ServerFactoryBean empFactory = new ServerFactoryBean();
		empFactory.setServiceClass(Employee.class);
		empFactory.setAddress("http://localhost:8080/cxf/employee");
		empFactory.setServiceBean(employee);
		AegisDatabinding empdb = new AegisDatabinding();
		empFactory.getServiceFactory().setDataBinding(empdb);
 
		DefaultTypeMappingRegistry emptmr = (DefaultTypeMappingRegistry) empdb.getTypeMappingRegistry();
		Configuration empconfiguration = emptmr.getConfiguration();
		empconfiguration.setDefaultMinOccurs(1);
		empconfiguration.setDefaultNillable(false);
 
		Server server = empFactory.create();
		Service service = server.getEndpoint().getService();
 
		service.getInInterceptors().add(new SAAJInInterceptor());
		service.getOutInterceptors().add(new SAAJOutInterceptor());
 
		wsIn = new WSS4JInInterceptor();
		wsIn.setProperty(WSHandlerConstants.SIG_PROP_FILE, "META-INF/cxf/insecurity.properties");
		wsIn.setProperty(WSHandlerConstants.DEC_PROP_FILE, "META-INF/cxf/insecurity.properties");
		wsIn.setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, TestPwdCallback.class.getName());
 
		service.getInInterceptors().add(wsIn);
 
		wsOut = new WSS4JOutInterceptor();
		wsOut.setProperty(WSHandlerConstants.SIG_PROP_FILE, "META-INF/cxf/outsecurity.properties");
		wsOut.setProperty(WSHandlerConstants.ENC_PROP_FILE, "META-INF/cxf/outsecurity.properties");
		wsOut.setProperty(WSHandlerConstants.USER, "myalias");
		wsOut.setProperty("password", "myAliasPassword");
		wsOut.setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, TestPwdCallback.class.getName());
		service.getOutInterceptors().add(wsOut);
 
		Map<String, Object> props = new HashMap<String, Object>();
		props.put("mtom-enabled", Boolean.TRUE);
 
		ClientProxyFactoryBean factory = new ClientProxyFactoryBean();
		factory.setServiceClass(HelloCxf.class);
		factory.setProperties(props);
		factory.setAddress("http://localhost:6210/cxf/hello_cxf");
		factory.getServiceFactory().setDataBinding(new AegisDatabinding());
		client = (HelloCxf) factory.create();
 
		ClientProxyFactoryBean empfactory = new ClientProxyFactoryBean();
		empfactory.setServiceClass(Employee.class);
		empfactory.setAddress("http://localhost:6210/cxf/employee");
		empfactory.getServiceFactory().setDataBinding(new AegisDatabinding());
		empClient = (Employee) empfactory.create();
 
		clientProxy = ClientProxy.getClient(empClient);
		wsIn.setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.ENCRYPT + " " + WSHandlerConstants.SIGNATURE);
		wsOut.setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.ENCRYPT + " " + WSHandlerConstants.SIGNATURE);
		clientProxy.getInInterceptors().add(wsIn);
		clientProxy.getInInterceptors().add(new SAAJInInterceptor());
		clientProxy.getOutInterceptors().add(wsOut);
		clientProxy.getOutInterceptors().add(new SAAJOutInterceptor());
 
	}
 
	public void testSetUpService() throws Exception {
		setUpService();
		assertNotNull(client);
		assertNotNull(empClient);
	}
 
	public void testCommunicateAndDataBinding() throws Exception {
		setUpService();
		assertEquals("Hello cxf", client.sayHi("cxf"));
		empClient.setName("kyong94");
		empClient.setTitle("JCF");
		assertEquals("kyong94", empClient.getName());
		assertEquals("JCF", empClient.getTitle());
		empClient.setProperty("kyong94", "Framework");
		assertEquals("name: kyong94 title: Framework", empClient.getProperty());
	}
 
	public void testEncryptionPlusSig() throws Exception {
		setUpService();
		empClient.setProperty("kyong94", "Framework");
		assertEquals("name: kyong94 title: Framework", empClient.getProperty());
	}
 
	public void testHashMap() throws Exception {
		setUpService();
		HashMap map = new HashMap();
		map.put("test", "testString");
		client.setStringMap("test", "testString");
		assertNotNull(client.getMap());
		assertEquals(map, client.getMap());
		System.out.println("setStringMap Result Value: " + client.getMap().get("test"));
		client.setMap(map);
		assertEquals(map, client.getMap());
		System.out.println("setMap Result Value: " + client.getMap().get("test"));
	}
 
	public void testModel() throws Exception {
		setUpService();
		TestModel model = new TestModel();
		model.setId("testId");
		model.setName("testName");
		model.setAge(20);
//		client.setModelData("testId", "testName", 20);
		client.setModel(model);
		System.out.println("ID: "+ client.getModel().getId() + " NAME: " + client.getModel().getName());
		assertNotNull(client.getModel());
		assertEquals(model, client.getModel());
	}
 
	public void testArrayList() throws Exception {
		setUpService();
		List list = new ArrayList();
		TestModel model1 = new TestModel();
		TestModel model2 = new TestModel();
		model1.setId("test1");
		model1.setName("testName1");
		model1.setAge(21);
		model2.setId("test2");
		model2.setName("testName2");
		model2.setAge(22);
		list.add(model1);
		list.add(model2);
		client.setList(list);
		assertNotNull(client.getList());
		assertEquals(list, client.getList());
		System.out.println("setList Result Value1: " +((TestModel) ((ArrayList)client.getList()).get(0)).getName());
		System.out.println("setList Result Value2: " +((TestModel) ((ArrayList)client.getList()).get(1)).getName());
	}
 
}

[편집] 실행결과

테스트를 수행한 결과가 다음 그림과 같이 모두 성공해야 한다.

그리고 콘솔창에는 서버와 클라이언트가 정상적인 값을 교환하는 것이 확인되어야 한다.

테스트를 수행할 때 TCP/IP 모니터를 활용하여 6210 포트에서 메시지를 캡처하면 각 요청에 대하여 다음과 같은 메시지가 교환되었음을 확인할 수 있다.

/cxf/employee

Request Data

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1"><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-19421516">
<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"/>
<ds:Reference URI="#id-5764479" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:Transforms xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"/>
<ds:DigestValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">WamLb/rjrgGx9Fd35rsKlO9kGPo=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
Lrr9NiBjGudiCGBWLZZjb9cEaglYY83QVAWtK+3rQ2kN5xiEWux+xQgzIj29b24StKq54LUUpek9
0Wib1spPvejNCyVi2Jcrk0u3dDJDTn1NRHAismX05BJ1UsJZ4olAxvBHYjLc00qO0YmE5zuHTkWl
5lKzwdtskCb9d7lEuf0=
</ds:SignatureValue>
<ds:KeyInfo Id="KeyId-26524183" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="STRId-21324485" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509IssuerSerial xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#">CN=myAlias</ds:X509IssuerName>
 
<ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#">1181668586</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data></wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature><xenc:EncryptedKey Id="EncKeyId-22210913" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509IssuerSerial xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#">CN=myAlias</ds:X509IssuerName>
 
<ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#">1181668586</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data></wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData><xenc:CipherValue>Vw1rxa03igrxURn35E03XP9NYp+kGa9wbpgln3ZDNBghq50GGJqxF3h6e+xyUwMaqZhKclY3QdP4e0zalxHDmIfd6pXCvFBnHcdA0vz9qcZQ2mm+Xwz9OHDe5gbaOlsYvRqU/ObQBQSotjeOJ+uCgiYCBPDvYG7ntSq9I9AR/oo=</xenc:CipherValue></xenc:CipherData>
<xenc:ReferenceList><xenc:DataReference URI="#EncDataId-5764479"/></xenc:ReferenceList></xenc:EncryptedKey></wsse:Security></soap:Header><soap:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="id-5764479"><xenc:EncryptedData Id="EncDataId-5764479" Type="http://www.w3.org/2001/04/xmlenc#Content" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/><xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"><xenc:CipherValue xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">/Y0Ra3YLVcxSHrP2J53RcqofO7ERYI6JNJMTT8WFcyYdlU0lpI/asn8td2spicK8kQH8WjXh7w6v
sguIBuXaNNRftBPKju0EpQjd8pWPzhWDa2C81I0Osm7vw2sWKj0vC8yDpXh4D/l2bvX+5Vwty6Zi
gFfsvSSzkxd1Khwd/oJ0fH0KznKP7xUprtN+ysM5wLghfPwzz88ogVY6qBgxfQOStisj84CM0JaV
R4J1Rvo+/bwLYYoxOOlOU9RoUvZh</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData></soap:Body></soap:Envelope>


Response Data

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1">
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-26509746">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#id-21197390">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>CofO0SjULaTypmj4SekusJccQZM=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
WAJ0F3PPToKg9Y5LPYFOWoBsG+RA8TUFdcZ3NutrQ6duDrxIVhCVcbPUvpUvQh6DxNcdYkk1C7EO
546i0lH+ZiwQBPMpl14jvszRfGTjUTbnBqA3W/FwGJkDK/M+4ZL0YJzTM48IhAwGKeqmdHAnf06p
eh+SZx7o7TztEUc9TLI=
</ds:SignatureValue>
<ds:KeyInfo Id="KeyId-7640118">
<wsse:SecurityTokenReference xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="STRId-28218271">
<ds:X509Data>
<ds:X509IssuerSerial>
<ds:X509IssuerName>CN=myAlias</ds:X509IssuerName>
 
<ds:X509SerialNumber>1181668586</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
<xenc:EncryptedKey Id="EncKeyId-33397973">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference>
<ds:X509Data>
<ds:X509IssuerSerial>
<ds:X509IssuerName>CN=myAlias</ds:X509IssuerName>
 
<ds:X509SerialNumber>1181668586</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>OlzWimdjQTqrGJWwhMNWPZ2UKYeMIN29wqeZBYPX+6fpzjaCuC+AcSgI48MM22yR/NXVtnRylOAcNUaLnAGO2eieb0oHiRYCq/VzoZgY1+wLN1jTuYFeZ+IcQdBrNz0vMjAKJ5tMxnSzYxsrpZ3VmwSXsypyWudAxp6HZTsEY6U=</xenc:CipherValue>
</xenc:CipherData>
<xenc:ReferenceList>
<xenc:DataReference URI="#EncDataId-21197390"/>
</xenc:ReferenceList>
</xenc:EncryptedKey>
</wsse:Security>
</soap:Header>
<soap:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="id-21197390">
<xenc:EncryptedData Id="EncDataId-21197390" Type="http://www.w3.org/2001/04/xmlenc#Content">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<xenc:CipherData>
<xenc:CipherValue>27oAubYlLvGGWavhAYF4dDm6vmC8uqbgAupjBZVcC92BgQ4mEuwYNzXzakiKebzz6aRLMtWo6isZ
nLYeJ1hvgN6bKv5x7ymwWo4YojZ20lQKM22eL/9YD3140GIAGzgNSKAKEc8deIyYWUCspdwFSP89
dGE7IHskuouKE48YYm+yFpVsrIPUpzdcamyO/RySZ3MpPZDMdtpMqBvnpIn2gAED1xWfUjFfIy8g
x38gjQq3YGZZWhTa4orzO/i1vugNJgNPIwz/MLemisZVipBf8SM+BD2+a4D/O0jDrEHUfBD+DYvK
B2EIBybEfDdOfge3LgxY+c3OYnoTSvP7CDL5KSRQsDZgvseWfl1/vcZldwU=</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soap:Body>
</soap:Envelope>

/cxf/hello_cxf

Request Data

------=_Part_0_11595238.1200513626201
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml; charset=UTF-8"
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>
 
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns1:getList xmlns:ns1="http://spring.demo/" /></soap:Body></soap:Envelope>
------=_Part_0_11595238.1200513626201--

Response Data

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns1:getListResponse xmlns:ns1="http://spring.demo/">
<ns1:return>
<ns2:TestModel xmlns:ns2="http://spring.demo">
<ns2:age>21</ns2:age>
<ns2:id>test1</ns2:id>
<ns2:name>testName1</ns2:name>
</ns2:TestModel>
<ns2:TestModel xmlns:ns2="http://spring.demo">
<ns2:age>22</ns2:age>
<ns2:id>test2</ns2:id>
<ns2:name>testName2</ns2:name>
</ns2:TestModel>
</ns1:return>
</ns1:getListResponse>
</soap:Body>
</soap:Envelope>

[편집] JCF3.0에 CXF 적용하기

[편집] 개발환경별 구성

[편집] Sun JDK(JRE)1.5.0_06 + eclipse europa + tomcat5.0

[편집] IBM WebSphere JRE6.1 + RAD7.0.0

다음 그림과 같이 $WebSphere_HOME/java/jre/lib/endorsed 폴더에 wsdl4j-1.6.1.jar를 복사한다.

WebSphere 관리콘솔에서 대상이 되는 특정한 어플리케이션을 발견하고 "Class loading and update detection"을 클릭한다.

  1. "Classes loaded with application class loader first"를 선택한다.
  2. "Class loader for each war file in application"를 선택한다.

이와 같이 설정한 이후에 서버를 재시작한다.

그림:check.gif

WebSphere6.1은 servlet-2.4를 사용하기 때문에 해당 어플리케이션의 classpath에 servlet-2.5 라이브러리가 존재하지 않는지 확인해라.

[편집] JBoss

  • 환경설정

만약 EAR 안에 WAR를 패키징했다면, classloader를 설정하기 위해 $EAR/META-INF에 jboss-app.xml와 같이 jboss을 위한 설정파일을 추가하는 것이 필요하다.

  • jboss-app.xml

<?xml version="1.0" encoding="UTF-8"?>
<jboss-app>
	<loader-repository>
		apache.cxf:loader=spring_http.ear
		<loader-repository-config>
			java2ParentDelegation=false
		</loader-repository-config>
	</loader-repository>
</jboss-app>

만약 QName 클래스와 관련된 연계에러(LinkageErrors)가 발생한다면, javax.xml.namespace.QName class를 제외하고 stax-api.jar를 다시 패키징한다 (적어도 JBoss 4.0.5GA의 경우). JBoss의 lib나 lib/endorsed 디렉터리에 QName 클래스와 충돌하는 버전의 클래스를 포함한다. 현재까지 JBoss 환경에서 CXF가 구동하도록 하는 유일한 해결책이다.

[편집] WebLogic

[편집] OC4J

  • 설정방법
  1. Oralce XML parser를 사용하지 않도록 설정한다.
  2. Xerces parser 라이브러리를 공유 라이브러리에 등록한다.

[편집] 참조