레이블이 Security인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Security인 게시물을 표시합니다. 모든 게시물 표시

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를 이용하여 메시지를 발신하고자 한다면 서비스 프로바이더의 인증서를 이용하여 메시지를 암호화하고, 자신의 비밀키로 메시지에 대한 서명을 한다. 서비스 요청을 받은 서비스 프로바이더는 클라이언트의 인증서를 이용하여 메시지에 대한 검증을 하고 자신의 비밀키로 메시지를 복호화한다. 메시지 검증과 복호화가 정상적으로 수행되었다면 원래 서비스에서 구현한 로직을 실행한다.
로직 실행 후 결과를 클라이언트에게 리턴하기 위해서 서비스 프로바이더는 클라이언트의 인증서를 이용하여 응답 메시지를 암호화하고, 자신의 비밀키로 메시지를 서명한다. 응답 메시지를 수신한 클라이언트는 서비스 프로바이더의 공개키로 메시지를 검증하고 자신의 비밀키로 메시지를 복호화한다.
각 종단에서 각각 자신의 비밀키와 상대방의 인증서를 이용하여 서명+암호화 및 검증+복호화가 실행되어야 한다.