레이블이 JAX-WS인 게시물을 표시합니다. 모든 게시물 표시
레이블이 JAX-WS인 게시물을 표시합니다. 모든 게시물 표시

2012년 7월 18일 수요일

CXF에서 Authorization 헤더 설정하기


CXF에서 HTTP Authorization 헤더에 Basic 방식으로 username, password를 전달할 때 아래와 같이 소스코드를 지정한다.



 org.apache.cxf.jaxws.JaxWsProxyFactoryBean clientFactory = new org.apache.cxf.jaxws.JaxWsProxyFactoryBean(); 
        clientFactory.setAddress("http://localhost:8080/svc-url"); // 서버 주소로 변경해 주세요
        clientFactory.setUsername(USERNAME); // username
        clientFactory.setPassword(PASSWORD); // password



HTTP Authorization 헤더의 Basic 방식인 
"username:password"가 base64 encoding 값으로 전달되는 것으로 확인되었습니다.

인증 정보 설정 후 요청 시 HTTP 요청 헤더 확인해 보시면 Authorization 헤더가 설정되어 있습니다.
 Authorization=[Basic Y2poY29uc3VtZXIxdidkxdxOndlbGNvbWUx]

2010년 7월 1일 목요일

CXF에서 WS-Security 사용하기.

Resources:

http://www.jroller.com/gmazza/entry/cxf_x509_profile

http://www.jroller.com/gmazza/entry/cxf_usernametoken_profile#UTCXF2

원문에서는 암호화 라이브러리인 bounce-castle을 설치하라고 설명하지만 예제에서 사용하는 암호화 앨거리듬(algorithm)을 사용하는 경우에는 굳이 설치가 필요하지 않다.

keystore에 key-pair 생성



keytool -genkey -alias myservicekey -keyalg RSA -sigalg SHA1withRSA -keypass skpass -storepass sspass -keystore serviceKeystore.jks -dname "cn=localhost"

keytool -genkey -alias myclientkey -keyalg RSA -sigalg SHA1withRSA -keypass ckpass -storepass cspass -keystore clientKeystore.jks -dname "cn=clientuser"


인증서 생성 후 keystore에 등록




keytool -export -rfc -keystore clientKeystore.jks -storepass cspass -alias myclientkey -file MyClient.cer

keytool -import -trustcacerts -keystore serviceKeystore.jks -storepass sspass -alias myclientkey -file MyClient.cer -noprompt


keytool -export -rfc -keystore serviceKeystore.jks -storepass sspass -alias myservicekey -file MyService.cer

keytool -import -trustcacerts -keystore clientKeystore.jks -storepass cspass -alias myservicekey -file MyService.cer -noprompt


인증서가 각각 서로의 종단 키스토어 파일에 등록되었기 때문에 인증서 파일은 삭제해도 상관없다. 키스토어 파일을 cxf에서 인식할 수 있도록 클래스 패스에 복사하도록 한다.
예를 들어 서비스 프로바이더 측에서는 serviceKeystore.jks 파일을 java/main/resources 에 복사한다.

서버측 웹서비스 개발 및 속성


스프링 설정

src/main/resources/spring/spring-config.xml


<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<!-- component scan -->
<context:component-scan base-package="sample.cxf" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>




src/main/resources/spring/cxf-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:cxf="http://cxf.apache.org/core"
xmlns:jaxrs="http://cxf.apache.org/jaxrs" 
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
">


<!-- Load CXF modules from cxf.jar -->
<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-jaxrs-binding.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

<!-- Enable message logging using the CXF logging feature -->
<cxf:bus>
<cxf:features>
<cxf:logging />
</cxf:features>
</cxf:bus>

<bean id="myPasswordCallback"
class="sample.cxf.webservice.server.ServerPasswordCallback"/>

<jaxws:endpoint id="userWss4jWebService"
implementor="#UserWebService"
address="/wss4jUserWebService">
<jaxws:outInterceptors>
<ref bean="TimestampSignEncrypt_Response"/>
</jaxws:outInterceptors>
<jaxws:inInterceptors>
<ref bean="TimestampSignEncrypt_Request"/>
</jaxws:inInterceptors>
</jaxws:endpoint>


<jaxws:endpoint id="userImageWebService"
implementor="#UserImageWebService"
address="/userImageWebService">
<jaxws:properties>
<entry key="mtom-enabled" value="true"/>
</jaxws:properties>
<jaxws:outInterceptors>
<ref bean="TimestampSignEncrypt_Response"/>
</jaxws:outInterceptors>
<jaxws:inInterceptors>
<ref bean="TimestampSignEncrypt_Request"/>
</jaxws:inInterceptors>
</jaxws:endpoint>

<bean id="TimestampSignEncrypt_Request"
class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="signaturePropFile" value="serviceKeystore.properties"/>
<entry key="decryptionPropFile" value="serviceKeystore.properties"/>
<entry key="passwordCallbackClass" value="sample.cxf.webservice.server.ServiceKeystorePasswordCallback"/>

</map>
</constructor-arg>
</bean>

<bean id="TimestampSignEncrypt_Response"
class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="user" value="myservicekey"/>
<entry key="signaturePropFile" value="serviceKeystore.properties"/>
<entry key="encryptionPropFile" value="serviceKeystore.properties"/>
<entry key="encryptionUser" value="useReqSigCert"/>
<entry key="passwordCallbackClass" value="sample.cxf.webservice.server.ServiceKeystorePasswordCallback"/>
<entry key="signatureParts" value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<entry key="encryptionParts" value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<entry key="encryptionSymAlgorithm" value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
</map>
</constructor-arg>
</bean>

<jaxws:endpoint id="userWebService" 
implementor="#UserWebService"
wsdlLocation="WEB-INF/wsdl/simpleUserWebService-usernametoken.wsdl"
address="/userWebService">

<jaxws:inInterceptors>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken"/>
<entry key="passwordType" value="PasswordText"/>
<entry key="passwordCallbackRef">
<ref bean="myPasswordCallback"/>
</entry>
</map>
</constructor-arg>
</bean>
</jaxws:inInterceptors>
</jaxws:endpoint>

</beans>




src/main/resources/serviceKeystore.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=sspass
org.apache.ws.security.crypto.merlin.keystore.alias=myservicekey
org.apache.ws.security.crypto.merlin.file=serviceKeystore.jks



serviceKeystore.jsk 파일이 src/main/resources 폴더에 저장되어 있어야 한다.

sample.cxf.webservice.server.ServiceKeystorePasswordCallback

package sample.cxf.webservice.server;

import java.io.IOException;

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 ServerPasswordCallback implements CallbackHandler {

@Override
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

if("joe".equals(pc.getIdentifier())) {
if(! pc.getPassword().equals("joespassword")) {
throw new IOException("Username/Password failure. - invalid password");
}
} else {
throw new IOException("Username/Password failure. - invalid username");
}
}
}



위 파일을 제외한 나머지 부분들은 일반 웹서비스 개발과 동일하다.
웹 컨텍스트 패스는 cxf-sample-project다. 클라이언트에서 서비스 정보를 설정할 때 컨텍스트 패스를 설정하므로 기억해 두어야 한다.

클라이언트 개발 및 설정


클라이언트 프로그램은 일반적으로 WSDL을 이용하여 CXF툴로 자바 소스를 생성한다. 일반적으로 생성되는 자바 소스의 패키지 명은 WSDL의 targetNamespace로 생성된다. 위의 경우에는 com.mycompany.sample.cxf.{subpackage}.. 로 생성된다.

src/main/resources/spring/spring-config.xml

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<!-- component scan -->
<context:component-scan base-package="com.mycompany.sample.cxf" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>





src/main/resources/spring/cxf-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:cxf="http://cxf.apache.org/core"
xmlns:jaxrs="http://cxf.apache.org/jaxrs" 
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
">


<!-- Load CXF modules from cxf.jar -->
<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-jaxrs-binding.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

<!-- Enable message logging using the CXF logging feature -->
<cxf:bus>
<cxf:features>
<cxf:logging />
</cxf:features>
</cxf:bus>

<bean id="userClient" class="com.mycompany.sample.cxf.webservice.user.UserWebService"
factory-bean="clientFactory" factory-method="create">
</bean>

<bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="serviceClass" value="com.mycompany.sample.cxf.webservice.user.UserWebService"></property>
<property name="address" value="http://localhost:8080/cxf-sample-project/ws/wss4jUserWebService"/>
<property name="inInterceptors">
<list>
<ref bean="TimestampSignEncrypt_Response"/>
</list>
</property>
<property name="outInterceptors">
<list>
<ref bean="TimestampSignEncrypt_Request"/>
</list>
</property>
</bean>

<bean id="userImageClient" class="com.mycompany.sample.cxf.webservice.userimage.UserImageWebService"
factory-bean="userImageClientFactory" factory-method="create">
</bean>

<bean id="userImageClientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="serviceClass" value="com.mycompany.sample.cxf.webservice.userimage.UserImageWebService"></property>
<property name="address" value="http://localhost:8080/cxf-sample-project/ws/userImageWebService"/>
<property name="inInterceptors">
<list>
<ref bean="TimestampSignEncrypt_Response"/>
</list>
</property>
<property name="outInterceptors">
<list>
<ref bean="TimestampSignEncrypt_Request"/>
</list>
</property>
</bean>

<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor" 
id="TimestampSignEncrypt_Request">
<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"></entry>
<entry key="user" value="myclientkey"></entry>
<entry key="signaturePropFile" value="clientKeystore.properties"/>
<entry key="encryptionPropFile" value="clientKeystore.properties"></entry>
<entry key="encryptionUser" value="myservicekey"/>
<entry key="passwordCallbackClass" value="com.mycompany.sample.cxf.webservice.user.ClientKeystorePasswordCallback"/>
<entry key="signatureParts" value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<entry key="encryptionParts" value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body"/>
<entry key="encryptionSymAlgorithm" value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
</map>
</constructor-arg>
</bean>

<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"
id="TimestampSignEncrypt_Response">
<constructor-arg>
<map>
<entry key="action" value="Timestamp Signature Encrypt"/>
<entry key="signaturePropFile" value="clientKeystore.properties"/>
<entry key="decryptionPropFile" value="clientKeystore.properties"/>
<entry key="passwordCallbackClass" value="com.mycompany.sample.cxf.webservice.user.ClientKeystorePasswordCallback"/>
</map>
</constructor-arg>
</bean>
</beans>



위의 설정 파일에서 서비스 경로를 주의 깊게 봐야 한다. 서버에서 설정한 컨텍스트 경로와 일치하는지 살펴봐야 한다.
클라이언트 서비스는 두 개다. 일반 SOAP 기반 메시지를 사용하는 웹서비스와 MTOM을 이용한 바이너리 파일 전송 웹서비스다.
실제로 WS-Security에 관련된 사항을 위해 클라이언트 코드가 변경되지는 않는다. CXF에서는 WSS4JInInterceptor와 WSS4JOutInterceptor 에 대한 설정만으로 WS-Security가 가능하다.

java/main/resources/clientKeystore.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=cspass
org.apache.ws.security.crypto.merlin.keystore.alias=myclientkey
org.apache.ws.security.crypto.merlin.file=clientKeystore.jks




com.mycompany.sample.cxf.webservice.user.ClientKeystorePasswordCallback

package com.mycompany.sample.cxf.webservice.user;

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;
import org.apache.ws.security.handler.WSHandlerConstants;

public class ClientKeystorePasswordCallback implements CallbackHandler {

private Map passwords = new HashMap();

public ClientKeystorePasswordCallback() {
super();

passwords.put("myclientkey", "ckpass");
}

@Override
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {

for(int i = 0; i < callbacks.length; i++) {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];

String pass = passwords.get(pc.getIdentifier());

if(pass != null) {
pc.setPassword(pass);
return;
}
}
}
}
WS-Security 클라이언트 샘플 - 일반 웹서비스
package com.mycompany.sample.cxf.webservice.user;

import javax.annotation.Resource;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.mycompany.sample.cxf.webservice.message.GetUserRequest;
import com.mycompany.sample.cxf.webservice.message.GetUserResponse;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath*:spring/*-config.xml"})
public class UserServiceClientWssClientTest {

@Resource(name="userClient")
protected UserWebService userClient;
@Test
public void testWss4J() throws Exception {
System.out.println("userClient: " + userClient);

Client client = ClientProxy.getClient(userClient);

client.getInInterceptors().add(new LoggingInInterceptor());
client.getOutInterceptors().add(new LoggingOutInterceptor());

GetUserRequest request = new GetUserRequest();
GetUserResponse response = userClient.getUser(request);

String code = response.getMessageStatus().getCode();
String message = response.getMessageStatus().getMessage();

System.out.println("=======> code: " + code + ", message: " + message);
}
}
WS-Security 클라이언트 샘플 - MTOM 웹서비스
package com.mycompany.sample.cxf.webservice.user;

import java.io.File;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.annotation.Resource;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.mycompany.sample.cxf.webservice.message.GetUserRequest;
import com.mycompany.sample.cxf.webservice.message.GetUserResponse;
import com.mycompany.sample.cxf.webservice.message.MessageStatus;
import com.mycompany.sample.cxf.webservice.userimage.UploadUserImageRequest;
import com.mycompany.sample.cxf.webservice.userimage.UploadUserImageResponse;
import com.mycompany.sample.cxf.webservice.userimage.UserImageWebService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath*:spring/*-config.xml"})
public class UserImageServiceClientWssClientTest {

@Resource(name="userImageClient")
protected UserImageWebService userImageClient;
@Test
public void testWss4J() throws Exception {
System.out.println("userImageClient: " + userImageClient);

Client client = ClientProxy.getClient(userImageClient);

client.getEndpoint().put("mtom-enabled", "true");

client.getInInterceptors().add(new LoggingInInterceptor());
client.getOutInterceptors().add(new LoggingOutInterceptor());


UploadUserImageRequest request = new UploadUserImageRequest();
request.setUploadUserId("kim");

DataSource dataSource = new FileDataSource(new File("d:/test3.zip"));
DataHandler image = new DataHandler(dataSource);
request.setUserImage(image);

UploadUserImageResponse response = userImageClient.uploadImage(request);

MessageStatus status = response.getStatus();

System.out.println("status: " + status);
if(status != null) {
String code = status.getCode();
String message = status.getMessage();

System.out.println("=======> code: " + code + ", message: " + message);

}
}
}
위의 소스 코드를 살펴보면 알겠지만 서버 소스와 클라이언트 소스에서는 WS-Security에 관련된 소스가 전혀 없다. CXF spring 설정 파일에서만 웹서비스 또는 웹서비스 클라이언트에 대한 설정만 하면 된다. 설사 WS-Security에 대한 정책이 변경된다 하더라도 소스 코드를 전혀 수정할 필요가 없는 것이다. 이클립스 개발환경에서 서비스 프로바이더 프로젝트(cxf-sample-project)를 톰켓 서버로 뛰운 상태에서 클라이언트 테스트 클래스를 JUnit으로 실행하면 console에 기록되는 메시지를 살펴보면 WS-Security 포맷의 SOAP 메시지로 변경되어 요청된다. WS-Security 요청 메시지 샘플
<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"><xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="EncKeyId-EBDC91EE354299835912779728815005"><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>
<ds:X509IssuerSerial>
<ds:X509IssuerName>CN=localhost</ds:X509IssuerName>
<ds:X509SerialNumber>1277784112</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data></wsse:SecurityTokenReference>
</ds:KeyInfo><xenc:CipherData><xenc:CipherValue>CvejtN49IQuTlqAwcYjR7vZ+vZgt21ysxsdwez35Iqb3NKGej7sjaoIvaCjsq4xnIooIhaXYcloksiEKijUUxN/EYQZgV2FxLwpO5uYi/9QgUqWrcwZqpjQuyLfDlVSM/18PeJmE0wAGRl1nGaCAAZlhEDB2tRhMJB49/xoh6p4=</xenc:CipherValue></xenc:CipherData><xenc:ReferenceList><xenc:DataReference URI="#EncDataId-4" /><xenc:DataReference URI="#EncDataId-5" /></xenc:ReferenceList></xenc:EncryptedKey><xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="EncDataId-4" Type="http://www.w3.org/2001/04/xmlenc#Element"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" /><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"><wsse:Reference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" URI="#EncKeyId-EBDC91EE354299835912779728815005" /></wsse:SecurityTokenReference>
</ds:KeyInfo><xenc:CipherData><xenc:CipherValue>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
kyB6VCR2k4E=</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-1"><wsu:Created>2010-07-01T08:27:59.078Z</wsu:Created><wsu:Expires>2010-07-01T08:32:59.078Z</wsu:Expires></wsu:Timestamp></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-3"><xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="EncDataId-5" Type="http://www.w3.org/2001/04/xmlenc#Content"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" /><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"><wsse:Reference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" URI="#EncKeyId-EBDC91EE354299835912779728815005" /></wsse:SecurityTokenReference>
</ds:KeyInfo><xenc:CipherData><xenc:CipherValue>2NJaIKrZg4TEzBySTG3gM1NwXFTHKCVkj/pTTuzKGMoOA3R8baccX+/sp2HxhsdW4zfePCNDADsd
1+gQ3aTaifVmSz8eSH4JvasEgbHYN/u79Xtg0pJI6/ekCQMPQHcL7aZ2un3RQeEAcZU9NzfLIg==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData></soap:Body></soap:Envelope>
WS-Security 응답 메시지 샘플
<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"><xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="EncKeyId-CD9ECFED812A2A37E512779728885785"><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>
<ds:X509IssuerSerial>
<ds:X509IssuerName>CN=clientuser</ds:X509IssuerName>
<ds:X509SerialNumber>1277784127</ds:X509SerialNumber>
</ds:X509IssuerSerial>
</ds:X509Data></wsse:SecurityTokenReference>
</ds:KeyInfo><xenc:CipherData><xenc:CipherValue>egeNxBQ54X7WmGE9LdLD9FwjQyoMF9fdjIXXlg9b8KKntxBRFtjO3fb51rws0IAybz7E0F//Rd5oqg1tB9KNkSrnKL4No5Vz9X1LTXZPszcT4YL2kh5mK7WZaui8dw/k2aODOXQw0o4CNGN61O3IOHq8HiMXUJ0ItohuBJCdTzA=</xenc:CipherValue></xenc:CipherData><xenc:ReferenceList><xenc:DataReference URI="#EncDataId-4" /><xenc:DataReference URI="#EncDataId-5" /></xenc:ReferenceList></xenc:EncryptedKey><xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="EncDataId-4" Type="http://www.w3.org/2001/04/xmlenc#Element"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" /><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"><wsse:Reference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" URI="#EncKeyId-CD9ECFED812A2A37E512779728885785" /></wsse:SecurityTokenReference>
</ds:KeyInfo><xenc:CipherData><xenc:CipherValue>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
DEnoFDCIi4Y=</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData><wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-1"><wsu:Created>2010-07-01T08:28:08.515Z</wsu:Created><wsu:Expires>2010-07-01T08:33:08.515Z</wsu:Expires></wsu:Timestamp></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-3"><xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="EncDataId-5" Type="http://www.w3.org/2001/04/xmlenc#Content"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" /><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"><wsse:Reference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" URI="#EncKeyId-CD9ECFED812A2A37E512779728885785" /></wsse:SecurityTokenReference>
</ds:KeyInfo><xenc:CipherData><xenc:CipherValue>SdmwkqGNhsspCdzDsKsUsHAfxr0xRTlYHt6r4n2HKTRvIIj7f8qhht7bNjSmqZTBDPV65irjtSVT
uGRyZERDEbSz5/3weCj6Wuag5y96HqruIRGxts4bexmZLAFZU+OaTjCAk/2t4jAMPHARk2S5/g==</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData></soap:Body></soap:Envelope>

WS-Security 웹서비스 보안

웹서비스 개발을 할 때마다 언제나 보안 문제에 직면하게 된다.
그나마 JAX-RS(RESTful)를 이용하는 경우에는 서비스 소비자(CLIENT)가 서비스 특성을 인정하여 메시지 보안에 대해서는 어느정도는 감안하고 사용하기도 하지만, B2B 업무용 웹서비스를 개발할 경우에는 높은 수준의 보안을 요구한다.
웹서비스에서 사실상 표준으로 자리잡은 WS-Security에 대해서 살펴보고 CXF에서 WS-Security를 이용하는 방법을 살펴보기로 하자.

WS-Security


WS-Security를 이해하기 위해서는 PKI 기반 암호화와 X509(인증서) 기반의 전자서명 개념을 알고 있어야 한다.

메시지 암복호화

비밀키가 노출되지 않는다는 가정하에서 PKI 기반 암복호화는 가장 안전한 암호화 수단이다. 복수의 다수에게 배포된 공개키로 암호화된 메시지는 비밀키로만 복호화가 가능하고, 비밀키로 암호화된 메시지는 공개키로만 복호화가 가능하다. 메시지 발신자는 수신자의 공개키(인증서의 공개키)로 암호화하여 메시지를 전송하면 통신 도중에 제 3자에 의해서 암호화된 메시지가 노출된다고 비밀키가 없기 때문에 메시지 내용을 볼 수가 없다.

전자서명
인증서란 전자서명을 하는 엔티티의 일반정보, 공개키 정보, 이슈어(Issuer) 정보들이 포함되어 있다. 전자서명이란 발신자가 메시지 내용에 대한 메시지 다이제스트를
생성하여 비밀키로 서명을 하면 수신자는 발신자의 인증서(인증서에 포함되어 있는 공개키)를 이용하여 메시지를 검증(verify)한다. 검증이 정상적으로 처리되었다면 아래와 같은 사실을 확신할 수 있다.

  • 메시지 인증 - 메시지 발신자에 대한 인증을 할 수 있다.

  • 무결성(integrity) - 발송된 메시지는 위.변조 되지 않았다.

  • 부인 방지 - 메시지 작성에 대한 부인을 방지할 수 있다.



웹서비스가 one-way 방식이 아닌 request-response 유형일 경우에는 클라이언트와 서비스 프로바이더가 각각 자신의 키페어(key-pair)와 상대방의 인증서를 키스토어에 저장하고 있어야 한다.
서비스 요청시 클라이언트는 발신자가 되고, 서비스 프로바이더는 수신자가 된다. 서비스 응답시에는 반대로 서비스 프로바이더가 발신자가 되고 클라이언트는 수신자가 된다.
서비스 요청시 클라이언트가 WS-Security를 이용하여 메시지를 발신하고자 한다면 서비스 프로바이더의 인증서를 이용하여 메시지를 암호화하고, 자신의 비밀키로 메시지에 대한 서명을 한다. 서비스 요청을 받은 서비스 프로바이더는 클라이언트의 인증서를 이용하여 메시지에 대한 검증을 하고 자신의 비밀키로 메시지를 복호화한다. 메시지 검증과 복호화가 정상적으로 수행되었다면 원래 서비스에서 구현한 로직을 실행한다.
로직 실행 후 결과를 클라이언트에게 리턴하기 위해서 서비스 프로바이더는 클라이언트의 인증서를 이용하여 응답 메시지를 암호화하고, 자신의 비밀키로 메시지를 서명한다. 응답 메시지를 수신한 클라이언트는 서비스 프로바이더의 공개키로 메시지를 검증하고 자신의 비밀키로 메시지를 복호화한다.
각 종단에서 각각 자신의 비밀키와 상대방의 인증서를 이용하여 서명+암호화 및 검증+복호화가 실행되어야 한다.

2010년 6월 29일 화요일

MTOM 가능한 웹서비스 개발(CXF)

Resources:
JAX-WS에서 MTOM 사용 가능

바이너리 파일 첨부를 사용하는 서비스에서 요청 객체에 DataHandler를 프로퍼티를 사용하여 바이너리 데이터를 전송할 수 있다. 해당 프로퍼티에 XmlMimeType을 "application/octet-stream"으로 지정한다.

요청 객체 샘플


package mypackage;

import javax.activation.DataHandler;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
@XmlType
public class UploadImeiFileRequest {

private String uploadUserId;


private DataHandler imeiFile;

public String getUploadUserId() {
return uploadUserId;
}
public void setUploadUserId(String uploadUserId) {
this.uploadUserId = uploadUserId;
}
@XmlMimeType("application/octet-stream")
public DataHandler getImeiFile() {
return imeiFile;
}
public void setImeiFile(DataHandler imeiFile) {
this.imeiFile = imeiFile;
}

}



Service Endpoint Interface


CXF에서는 BindingType을 어노테이션을 반드시 지정해 주어야 할 필요는 없지만 웹서비스를 명시적으로 MTOM 가능하도록 나타내도록 한다. 또한 MTOM 어노테이션을 사용하기도 한다.
package com.mycompany.ws;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.ws.BindingType;

import com.sds.shp.webservice.message.UploadImeiFileRequest;
import com.sds.shp.webservice.message.UploadImeiFileResponse;

@WebService(name="imeiFileWebService", 
serviceName="imeiFileWebService",
targetNamespace="http://mocompany.com/ws/fileupload")
@BindingType(javax.xml.ws.soap.SOAPBinding.SOAP11HTTP_MTOM_BINDING)  
@javax.xml.ws.soap.MTOM
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)  
public interface FileUploadWebService{

@WebMethod(operationName="uploadImeiFile")
@WebResult(name="UploadUserImageResponse")
UploadImeiFileResponse uploadImeiFile(@WebParam(name="UploadImeiFile") UploadImeiFileRequest request) throws Exception;


}



서비스 cxf 설정 파일


jaxws:endpoint의 jaxws:properties 의 "mtom-enabled" 속성을 "true"로 지정한다.

<jaxws:endpoint id="fileUploadService" 
implementor="com.mycompany.ws.FileUploadWebServiceImpl"
address="/fileUploadWebService">
<jaxws:properties>
<entry key="mtom-enabled" value="true"/>
</jaxws:properties>
</jaxws:endpoint>


클라이언트 샘플1. Dispatch 사용


CXF를 전혀 사용하지 않고 Java 표준 api만으로 파일 첨부 클라이언트를 구현한 예제다.

클라이언트 샘플2. CXF의 JaxWsProxyFactoryBean 사용


CXF 환경 설정 없이 SEI 만으로 클라이언트를 생성하여 사용한다.

클라이언트 샘플3. 스프링 빈 등록 후 클라이언트 사용


스프링 설정 파일에 클라이언트를 등록한 후 Injection 객체를 사용한다.

package com.sds.shp.webservice.server;

import java.io.File;
import java.net.URL;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.annotation.Resource;
import javax.xml.namespace.QName;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;

import org.apache.cxf.common.util.SOAPConstants;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import mypackage.LogManager;
import mypackage.PropertyNames;
import mypackage.UploadImeiFileRequest;
import mypackage.UploadImeiFileResponse;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:/spring/context-*.xml" })
public class ImeiFileWebServiceClientTest {


String endpointAddress = "http://mycompany.com/endpointuri";

String nsURI = "http://mycompany.com/ws/fileupload";

@Test
public void testDispatch() throws Exception {
MessageFactory factory = MessageFactory.newInstance();

QName serviceName = new QName(nsURI, "imeiFileWebService");
QName portName = new QName(nsURI, "imeiFileWebServicePort");

Service service = Service.create(new URL(endpointAddress + "?wsdl"), serviceName);
service.addPort(portName, SOAPBinding.SOAP11HTTP_MTOM_BINDING, endpointAddress);

SOAPMessage soapRequest = factory.createMessage();
SOAPBody body = soapRequest.getSOAPBody();

QName uploadImeiFileQName = new QName(nsURI, "uploadImeiFile", "ime");
SOAPBodyElement payload = body.addBodyElement(uploadImeiFileQName);

QName inParamName = new QName("UploadImeiFile");
SOAPElement inParamElement = payload.addChildElement(inParamName);

QName imeiName = new QName("imeiFile");
SOAPElement imeiElement = inParamElement.addChildElement(imeiName);

QName xopIncludeName = new QName("http://www.w3.org/2004/08/xop/include", "Include", "xop");
SOAPElement includeElement = imeiElement.addChildElement(xopIncludeName);

String cid = "" + System.currentTimeMillis();
includeElement.addAttribute(new QName("href"), "cid:" + cid);

QName uploadUserIdName = new QName("uploadUserId");
SOAPElement uploadUserIdElement = inParamElement.addChildElement(uploadUserIdName);
uploadUserIdElement.setTextContent("xxxx");



Dispatch dispatch = service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE);

BindingProvider bp = (BindingProvider) dispatch;
((SOAPBinding) bp.getBinding()).setMTOMEnabled(true);

DataSource ds = new FileDataSource("d:/test2.zip");
DataHandler dataHandler = new DataHandler(ds);

AttachmentPart attachmentPart = soapRequest.createAttachmentPart(dataHandler);
attachmentPart.setContentId(cid);
soapRequest.addAttachmentPart(attachmentPart);


//    System.out.println("=== SOAP REQUEST ===");
//    soapRequest.writeTo(System.out);
//    System.out.println("=====================");

long start = System.currentTimeMillis();

dispatch.invoke(soapRequest);

System.out.println("==========> elapsed: " + (System.currentTimeMillis() - start));
}

@Test
public void testClientProxyFactoryBean() throws Exception {
long start = System.currentTimeMillis();
JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();

java.util.Map props = new java.util.HashMap();
props.put(SOAPConstants.MTOM_ENABLED, Boolean.TRUE.toString());

factoryBean.setProperties(props);
factoryBean.setServiceClass(IImeiFileWebService.class);
factoryBean.setAddress(endpointAddress);

IImeiFileWebService service = (IImeiFileWebService) factoryBean.create();


// start.

boolean useProxy = false;

if(useProxy) {
String httpProxyServer = "proxy-ip";
int httpProxyServerPort = 8080;
int httpConnectionTimeout = 30000;
boolean httpAllowChunking = false;


Client client = ClientProxy.getClient(service);
client.getEndpoint().put( "mtom-enabled", "true" );

if(LogManager.isDebugEnabled()) {
String ln = System.getProperty("line.separator");
StringBuffer sb = new StringBuffer(ln);

sb.append("###############################################").append(ln);
sb.append(PropertyNames.HTTP_PROXY_SERVER).append(": ").append(httpProxyServer).append(ln);
sb.append(PropertyNames.HTTP_PROXY_SERVER_PORT).append(": ").append(httpProxyServerPort).append(ln);
sb.append(PropertyNames.HTTP_CONNECTION_TIMEOUT).append(": ").append(httpConnectionTimeout).append(ln);
sb.append(PropertyNames.HTTP_ALLOW_CHUNKING).append(": ").append(httpAllowChunking).append(ln);
sb.append("###############################################").append(ln);

LogManager.debug(getClass(), sb.toString());
}

HTTPConduit http = (HTTPConduit) client.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(httpConnectionTimeout);
httpClientPolicy.setAllowChunking(httpAllowChunking);
httpClientPolicy.setProxyServer(httpProxyServer);
httpClientPolicy.setProxyServerPort(httpProxyServerPort);
http.setClient(httpClientPolicy);

}   

// end.

UploadImeiFileRequest request = new UploadImeiFileRequest();
request.setUploadUserId("abc");

//    DataSource dataSource = new FileDataSource(new File("d:/test2.zip"));
DataSource dataSource = new FileDataSource(new File("d:/test2.zip"));
DataHandler dataHandler = new DataHandler(dataSource);

request.setImeiFile(dataHandler);

UploadImeiFileResponse response = service.uploadImeiFile(request);
System.out.println((System.currentTimeMillis() - start) + " milli seconds" );
}

@Resource(name="imeiFileClient")
IImeiFileWebService client;
@Test
public void testImeiFileWebServiceClient() throws Exception {
try {
long start = System.currentTimeMillis();
UploadImeiFileRequest request = new UploadImeiFileRequest();
request.setUploadUserId("xx");

DataSource dataSource = new FileDataSource(new File("d:/test2.zip"));
DataHandler dataHandler = new DataHandler(dataSource);

request.setImeiFile(dataHandler);

UploadImeiFileResponse response = client.uploadImeiFile(request);

System.out.println((System.currentTimeMillis() - start) + " milli seconds" );

} catch(Exception ex) {
ex.printStackTrace();
throw ex;
}
}

}



Injection된 객체를 사용하기 위해서는 스프링 설정파일에 아래와 같이 클라이언트를 등록해야 한다.

아래와 같이 클라이언트 빈을 지정해 주면 된다.
mtom-enabled속성을 true로 지정한다.
주의할 점은 endpointName과 serviceName을 namespace를 포함하여 반드시 지정하기를 권장한다. 개발 환경(개발자피씨)에서는 endpointName(PortName)와 serviceName을 지정하지 않아도 서비스와 연동하는데 문제가 되지 않지만, 외부 서버로 서비스를 디플로이 한 후에 연동하고자 할 때 문제가 발생한다.

<jaxws:client id="imeiFileClient" xmlns:tns="http://mocompany.com/ws/fileupload"
serviceClass="com.mycompany.ws.FileUploadWebService"
address="http://mycompany.com/ws/fileUploadWebService"
endpointName="tns:imeiFileWebServicePort"
serviceName="tns:imeiFileWebService">
<jaxws:properties>
<entry key="mtom-enabled" value="true"/>
</jaxws:properties>

</jaxws:client>




Sample Soap message


Payload: 
--uuid:08654ae5-c060-47dd-9bee-48dff0090a65
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml";
Content-Transfer-Encoding: binary
Content-ID: <root.message@cxf.apache.org>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:uploadImeiFile 
xmlns:ns2="http://socialhub.samsungmobile.com/store/ws/imeifile" 
xmlns:ns3="http://socialhub.samsungmobile.com/store/ws">
<UploadImeiFile><imeiFile>
<xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" 
href="cid:3381b3c2-d57e-490a-bccb-7c91487f24b2-1@http%3A%2F%2Fcxf.apache.org%2F"/></imeiFile>
<uploadUserId>xx</uploadUserId></UploadImeiFile>
</ns2:uploadImeiFile></soap:Body>
</soap:Envelope>
--uuid:08654ae5-c060-47dd-9bee-48dff0090a65
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
Content-ID: <3381b3c2-d57e-490a-bccb-7c91487f24b2-1@http://cxf.apache.org/>

0? ?0? ??      K? ?0   *?H?8   

--uuid:08654ae5-c060-47dd-9bee-48dff0090a65--

2010년 4월 15일 목요일

JAX-WS client 작성


  • Dispatch를 이용한 클라이언트 작성

  • jaxws:client를 이용한 클라이언트 작성

  • JaxWsProxyFactoryBean를 이용한 클라이언트 작성


CXF를 이용하여 웹서비스를 개발할 때 JAX-WS 클라이언트 만드는 방법을 이야기하고자 한다.
CXF가 Spring기반의 프레임웍이기 때문에 빈을 등록하여 사용하는 방법과 SOAP 메시지를 만들어서 사용하는 Java 표준 API(?) 방식을 비교해 보기 바란다.

이해를 돕기 위해 요청메시지와 응답메시지를 먼저 살펴보도록 한다.

요청 메시지
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.demo.amf.archnal.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <ws:getUser>
         <userNo>1</userNo>
      </ws:getUser>
   </soapenv:Body>
</soapenv:Envelope>


응답 메시지
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <ns2:getUserResponse xmlns:ns2="http://ws.demo.amf.archnal.com/">
         <return>
            <email>email1@company.com</email>
            <loginId>loginid1</loginId>
            <loginPassword>passwd1</loginPassword>
            <name>name1</name>
            <userNo>1</userNo>
         </return>
      </ns2:getUserResponse>
   </soap:Body>
</soap:Envelope>


JAVA API를 이용하여 클라이언트 작성


아래는 WSDL 정보가 있다면 개발자가 아래와 같이 클라이언트를 작성할 수 있다.

표준 JAVA API 클라이언트
  public void testInvoke() throws Exception {
    String endpointAddress = "http://localhost:8080/demo/amfesb/sampleUserWs";
    
    QName serviceName = new QName("http://ws.demo.amf.archnal.com/", "SampleUserWsImplService");
    QName portName = new QName("http://ws.demo.amf.archnal.com/", "SampleUserWsImplPort");
    
    Service service = Service.create(serviceName);
    
    service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
    
    Dispatch dispatch = service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE);
    BindingProvider bp = (BindingProvider) dispatch;
    MessageFactory factory = ((SOAPBinding) bp.getBinding()).getMessageFactory();
    
    SOAPMessage request = factory.createMessage();
    SOAPBody body = request.getSOAPBody();
    QName payloadName = new QName("http://ws.demo.amf.archnal.com/", "getUser", "ws");
    SOAPBodyElement payload = body.addBodyElement(payloadName);
    
    
    QName userNoName = new QName("userNo");
    SOAPElement userNo = payload.addChildElement(userNoName);
    userNo.addTextNode("1");

    SOAPMessage reply = null;
    
    try {
      reply = dispatch.invoke(request);
    } catch(WebServiceException ex) {
      ex.printStackTrace();
    }
    
    body = reply.getSOAPBody();
    QName responseName = new QName("http://ws.demo.amf.archnal.com/", "getUserResponse");
    SOAPElement bodyElement = (SOAPElement) body.getChildElements(responseName).next();
    String responseMessageText = bodyElement.getTextContent();
    
  }


CXF 클라이언트 작성


CXF로 jaxws 클라이언트를 작성하기 위해서 SEI(Service Endpoint Interface)가 필요하다. SEI 클래스를 가지고 있지 않다고 하더라고 문제될 건 없다. http://credemol.blogspot.com/2010/04/cxf-codegen-plugin-maven.html 글에서 설명했던 것처럼 WSDL만 있으면 관련된 소스 코드를 자동으로 생성해 낼 수 있기 때문이다.

spring 빈 설정
<?xml version="1.0" encoding="UTF-8"?>
<beans
     xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:jaxws="http://cxf.apache.org/jaxws"
     xmlns:cxf="http://camel.apache.org/schema/cxf"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://cxf.apache.org/jaxws 
    http://cxf.apache.org/schemas/jaxws.xsd
    http://camel.apache.org/schema/cxf 
    http://camel.apache.org/schema/cxf/camel-cxf.xsd
    ">
    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-jaxws.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-http.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-addr.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />

  <jaxws:client id="sampleUserWsBiz" 
    serviceClass="com.archnal.amf.demo.ws.SampleUserWs"
    address="http://localhost:8080/demo/amfesb/sampleUserWs">
  </jaxws:client>
  
 
</beans>


위와 같이 spring 빈을 설정하였다면 client 소스코드는 아래와 같이 작성하면 된다.
getBean()은 스프링 빈 객체를 리턴하는 메서드다.

CXF 클라이언트
  public void testSampleUserWsBiz() {
    SampleUserWs ws = (SampleUserWs) getBean("sampleUserWsBiz");
    assertNotNull(ws);
    
    SampleUser user = ws.getUser(1L);
    assertNotNull(user);
  }


JaxWsProxyFactoryBean를 이용한 클라이언트 작성


jaxws:client 설정을 이용하여 클라이언트를 작성하면 여러가지로 편리한 점이 있지만, endpoint uri를 설정에서만 지정하기 때문에 uri를 동적으로 지정하고자 할 때 문제가 발생한다.
하지만 JaxWsProxyFactoryBean는 address가 프로퍼티이기 때문에 스프링 설정에서 지정할 수 있고 또한 호출시에 동적으로 설정할 수도 있다.

아래의 소스코드를 살펴보자.
호출할 때마다 address(endpoint uri)를 지정하여 서비스를 호출하는 예제다.

cxf configuration
   <bean id="userProxyFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
      <property name="serviceClass" value="com.sds.shp.webservice.server.IUserWebService"/>
      <!-- 
      <property name="address" value="http://localhost:9002/HelloWorld"/>
       -->
    </bean>


서비스 호출 코드
package com.sds.shp.webservice.client;

import javax.annotation.Resource;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.sds.shp.domain.AuthWebservice;
import com.sds.shp.webservice.message.PutAccountInfoRequest;
import com.sds.shp.webservice.message.PutAccountInfoResponse;
import com.sds.shp.webservice.server.IUserWebService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:/spring/context-*.xml" })

public class UserWebServiceClient {
  @Resource(name="userProxyFactory")
  private JaxWsProxyFactoryBean userProxyFactory;
  
  @Test
  public void testJaxWsProxyFactoryBean() throws Exception {
    userProxyFactory.setAddress("http://70.7.105.245:8080/store/ws/userWebService");
    IUserWebService userWebService = (IUserWebService) userProxyFactory.create();

    
    System.out.println("userWebService: " + userWebService);
    
    AuthWebservice authToken = new AuthWebservice();
    authToken.setTokenId("token");
    authToken.setTokenPasswd("pw");
    
    PutAccountInfoRequest request = new PutAccountInfoRequest();
    PutAccountInfoResponse response = userWebService.putAccountInfo(authToken, request);
    
    System.out.println("code: " + response.getResultStatus().getErrorCode());
    
  }
}


위의 소스 코드는 JaxWsProxyFactoryBean을 스프링 환경 설정 파일로 등록 한 후 테스트 코드에서 등록된 리소스를 Injection 받아서 사용하는 예제다. 호출할 때마다 address를 설정해서 사용하기 때문에 동적으로 endpoint uri를 지정하도록 되어 있지만 JaxWsProxyFactoryBean 빈을 여러 쓰레드에서 함께 사용하기 때문에 쓰레드에 안전하지 않게 작동될 가능성이 높다.
스프링에 등록하지 않고 실행될 때마다 JaxWsProxyFactoryBean 빈을 생성하는 아래의 소스코드를 참조하기 바란다. 매 요청시마다 새로운 JaxWsProxyFactoryBean이 생성되기 때문에 멀티쓰레드 환경에서도 안전하게 실행될 수 있다.
  @Test
  public void testJaxWsProxyFactoryBean2() throws Exception {
    JaxWsProxyFactoryBean proxyFactoryBean = new JaxWsProxyFactoryBean();
    proxyFactoryBean.setServiceClass(IUserWebService.class);
    proxyFactoryBean.setAddress("http://70.7.105.245:8080/store/ws/userWebService");
    IUserWebService userWebService = (IUserWebService) proxyFactoryBean.create();

    
    System.out.println("userWebService: " + userWebService);
    
    AuthWebservice authToken = new AuthWebservice();
    authToken.setTokenId("token");
    authToken.setTokenPasswd("pw");
    
    PutAccountInfoRequest request = new PutAccountInfoRequest();
    PutAccountInfoResponse response = userWebService.putAccountInfo(authToken, request);
    
    System.out.println("code: " + response.getResultStatus().getErrorCode());
    
  }

2010년 4월 5일 월요일

CXF에서 jaxws:endpoint 설정하기

Resources:
http://cxf.apache.org/docs/jax-ws-configuration.html

jaxws:endpoint CXF에서 웹서비스(jax-ws)를 설정하는 엘리먼트다.
jaxws:endpoint는 아래와 같은 속성을 설정할 수 있다.
id: spring id
implementor: 웹서비스 구현 클래스명을 입력한다. 만약 웹서비스 구현 클래스를 별도의 스프링 빈으로 설정했다면 스프링빈아이디앞에 '#'을 붙혀셔 지정한다.
address: 절대 웹 URL을 입력해도 되지만 상대 경로로 지장할 수 있다. 예를들면 서블릿 컨텍스트가 /cxfsample이고 web.xml 에 지정한 servlet url mapping이 /service/* 인 경우에 address를 /sampleWs로 지정하였다면 서비스 경로는 /cxfsample/service/sampleWs 이 된다.
endpointName: port의 name 속성을 설정한다.

보통 cxf를 이용하여 웹서비스를 개발하는 경우에 스프링에 빈을 등록하기 때문에 보통 인터페이스와 구현클래스를 모두 개발하게 된다.
하지만 일반적으로 웹서비스와 관련된 annotation은 인터페이스에 설정하여 개발하는데 이렇게 되면 약간의 문제가 발생한다.
jaxws:endpoint로 설정하는 경우는 Java first 웹서비스 개발을 하게 되어 WSDL을 자동 생성되도록 하는데 CXF는 WSDL을 생성하는데 인터페이스에 설정된 annotation을 이용하지 않고 구현클래스에 있는 annotation을 이용한다.
만약 구현클래스에서 @WebService annotation을 설정하지 않았다면 WSDL을 아래와 같이 생성될 것이다.

생성된 WSDL 샘플

<wsdl:definitions name="SampleUserWsImplService" targetNamespace="http://ws.demo.amf.archnal.com/">
<wsdl:import location="http://localhost:8080/demo/amfesb/sampleUserWs?wsdl=sampleUserWs.wsdl" namespace="http://www.archnal.com/amf/demo/ws">
</wsdl:import>
...


위의 WSDL은 웹서비스를 제공하는데는 아무런 문제가 없지만 사소한 몇가지 문제가 있다.
우선 wsdl:definition의 targetNamespace가 우아하지 않다. 일반적으로 지정하는 namespace URI 형식이 아니다. 구현클래스의 패키지명을 역순으로 쭉 연결해 놓은 모양새가 보기에 좋지 않다. 두번째 문제로는 스키마를 동일 WSDL에 표현하지 않고 import 시키고 있다는 것이다. 원인은 서로 namespace가 다르기 때문이다. 네임스페이스를 동일하게 맞춰주면 단일 WSDL로 서비스에 대한 표현이 가능하다.

구현 클래스의 클래스 레벨에 아래와 같은 annotation을 지정하면 wsdl:definitions의 targetNamespace가
http://www.archnal.com/amf/demo/ws로 변경될 것이다.

@WebService(name="SampleUserWs"
,serviceName="SampleUserWs"
,targetNamespace="http://www.archnal.com/amf/demo/ws"
)







설정파일 샘플

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<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-servlet.xml" />

<jaxws:endpoint
id="sampleUserWs"
implementor="#sampleUserWsBean"
address="/sampleUserWs" >

</jaxws:endpoint>
<bean
id="sampleUserWsBean"
class="com.archnal.amf.amfws.ws.SampleUserWsImpl">
<property name="sampleUserService" ref="sampleUserService">
</property>
</bean>
</beans>

2010년 3월 26일 금요일

CXF 웹 서비스 개발

1. web.xml


web.xml 파일에서는 cxf를 실행할 수 있는 환경을 설정해 주어야 한다.
아래의 샘플 파일에서는 스프링 설정 파일로 WEB-INF/beans.xml 파일을 설정하고 있다.
또한 웹 경로의 /services/* 로 시작하는 URL 은 CXFServlet으로 제어를 넘긴다.

web.xml

----------------------------------------------------------
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/beans.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<display-name>CXF Servlet</display-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>
</web-app>

----------------------------------------------------------


2. beans.xml


beans.xml 파일에서는 CXF에서 실행할 웹서비스 정보를 설정해 주면 된다.
아래의 경우에 /services/OrderProcess 라고 요청이 들어오면 demo.order.OrderProcessImpl
클래스의 특정 메서드가 실행된다.

beans.xml
----------------------------------------------------------

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<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-servlet.xml" />
<jaxws:endpoint
id="orderProcess"
implementor="demo.order.OrderProcessImpl"
address="/OrderProcess" />
</beans>
----------------------------------------------------------



3. Endpoint Service Interface


Interface : OrderProcess


----------------------------------------------------------
package demo.order;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService
public interface OrderProcess {

@WebMethod
String processOrder(Order order);
}
----------------------------------------------------------


4. Service 구현 클래스



----------------------------------------------------------
package demo.order;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService()
public class OrderProcessImpl implements OrderProcess {

@Override
public String processOrder(Order order) {
String orderID = validate(order);
return orderID;
}
@WebMethod(action="1", exclude=false)
private String validate(Order order) {
String custID = order.getCustomID();
String itemID = order.getItemID();
int qty = order.getQty();
double price = order.getPrice();

if(custID != null && itemID != null && !custID.equals("") &&
!itemID.equals("") && qty > 0 && price > 0.0) {
return "ORD1234";
}
return null;
}

}
----------------------------------------------------------


5. Soap Message


Soap Body로 리턴되는 클래스는 XmlRootElement annotation을 반드시 선언해 주어야 한다.

----------------------------------------------------------
package demo.order;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Order")
public class Order {
private String customID;
private String itemID;
private int qty;
private double price;

public Order() {
super();
}

public String getCustomID() {
return customID;
}

public void setCustomID(String customID) {
this.customID = customID;
}

public String getItemID() {
return itemID;
}

public void setItemID(String itemID) {
this.itemID = itemID;
}

public int getQty() {
return qty;
}

public void setQty(int qty) {
this.qty = qty;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}
}
----------------------------------------------------------


6. 결과


이렇게 구성된 web application을 cxfsample-web.war 파일로 묶어서 정상적으로 톰켓에 디플로이 시켰다면,
http://localhost:8080/cxfsample-web/services/OrderProcess?wsdl 라고 웹브라우저의 주소창에 입력하였다면 화면에서 본 서비스의 WSDL 내용을 볼 수 있다.

7. 테스트


Soap 메시지 기반의 웹서비스 테스트는 SoapUI를 이용해서 테스트 할 수 있다.

REQUEST MESSAGE SAMPLE

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ord="http://order.demo/">
<soapenv:Header/>
<soapenv:Body>
<ord:processOrder>
<arg0>
<customID>u111</customID>
<itemID>a111</itemID>
<price>100</price>
<qty>10</qty>
</arg0>
</ord:processOrder>
</soapenv:Body>
</soapenv:Envelope>


RESPONSE MESSAGE Sample

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:processOrderResponse xmlns:ns2="http://order.demo/">
<return>ORD1234</return>
</ns2:processOrderResponse>
</soap:Body>
</soap:Envelope>