Cxf+wss4j的WS-Security实现

最近一个项目预研,需要使用webservice,进行消息安全传输,客户要求包括加密和认证。我们使用的ws框架是cxf,认证是很容易做的,通过自定义实现一个Interceptor,就可以进行简单的用户和密码认证。但加密从来没有做过,在网上做了一些工作之后,发现cxf是可以通过wss4j实现签名、加密的。
根据网上的资料,做了一个demo,上传位置在:https://download.youkuaiyun.com/download/wangchsh2008/3539244 ,下载即可直接运行。
下面就过程,做一个简单说明。
 

WSS4J支持如下几种模式:

XML Security

    XML Signature

    XML Encryption

Tokens

    Username Tokens

    Timestamps

    SAML Tokens

这里将使用Timestamps+Encryption+Signature组合。  关于wss4j的学习,请参考apache文档。

第一步是要生成服务端及客户端密钥文件,这里用到了JDK中的keytool工具,关于Keytool的使用以及参数说明,请读者自行学习相关文档。
本文中为了方便起见,已经将命令编辑为2个bat文件。在同一目录下,执行generateServerKey.bat批处理,即可生成clientStore.jks及serverStore.jks文件(clientKey.rsa和serverKey.rsa文件用不到)。
generateKeyPair.bat

rem @echo off
echo alias %1
echo keypass %2
echo keystoreName %3
echo KeyStorePass %4
echo keyName %5

echo keyName %5
keytool -genkey -alias %1 -keypass %2 -keystore %3 -storepass %4  -dname "cn=%1" -keyalg RSA
keytool -selfcert -alias %1 -keystore %3 -storepass %4 -keypass %2
keytool -export -alias %1 -file %5 -keystore %3 -storepass %4


generateServerKey.bat

 

call generateKeyPair.bat apmserver apmserverpass serverStore.jks keystorePass serverKey.rsa
call generateKeyPair.bat apmclient apmclientpass clientStore.jks keystorePass clientKey.rsa
keytool -import -alias apmserver -file serverKey.rsa -keystore clientStore.jks -storepass keystorePass -noprompt
keytool -import -alias apmclient -file clientKey.rsa -keystore serverStore.jks -storepass keystorePass -noprompt

 

 

 

生成的密钥文件中包含的信息:

服务端 账户:apmserver / apmserverpass

客户端 账户:apmclient / apmclientpass


如下图所示建立工程:


lib下的jar:


服务器端,有一个对密码进行处理的类 PasswordHandler.java

 

package com.db.webservice.security;

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;

/**
 * desc: comment PasswordHandler.java
 * @author Chaisson(chengshengwang)
 * @since Aug 19, 2011 5:08:30 PM
 * @vision 1.0
 */
public class PasswordHandler implements CallbackHandler {

	private Map<String, String> passwords = new HashMap<String, String>();

	public PasswordHandler() {
		passwords.put("apmserver", "apmserverpass");
        passwords.put("apmclient", "apmclientpass");
	}

	public void handle(Callback[] callbacks) throws IOException,
			UnsupportedCallbackException {
		WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
		String id = pc.getIdentifer();
		pc.setPassword((String) passwords.get(id));
	}
}


定义一个interface:TestService.java

 

package com.db.webservice;

import javax.jws.WebParam;
import javax.jws.WebService;

/**
 * desc: comment TestService.java
 * @author Chaisson(chengshengwang)
 * @since May 13, 2011 11:41:03 AM
 * @vision 1.0
 */
@WebService
public interface TestService {
	public String sayHello(@WebParam(name="myName") String name);
	
	public String printMan(User user);
}


实现上面的接口TestServiceImpl.java

 

package com.db.webservice.impl;

import com.db.webservice.TestService;
import com.db.webservice.User;

/**
 * desc: comment TestServiceImpl.java
 * @author Chaisson(chengshengwang)
 * @since May 13, 2011 11:41:38 AM
 * @vision 1.0
 */   
public class TestServiceImpl implements TestService {
	public String sayHello(String myName){
		System.out.println("Hello World! "+myName);
		return "SUCCESS";
	}

	public String printMan(User user) {
		StringBuffer sb = new StringBuffer();
		if(user.getAge()>=18 && user.getAge()<60){
			sb.append("He is a young man. ");
		}else if(user.getAge()>=60){
			sb.append("He is an old man. ");
		}else{
			sb.append("He is a little boy. ");
		}
		
		if(user.getName()!=null){
			sb.append(" His name is "+user.getName()+". ");
		}
		if(user.getDesc()!=null){
			sb.append(" His description is that "+user.getDesc()+". ");
		}
		return sb.toString();
	}
}


一个PO: User.java

 

package com.db.webservice;

/**
 * desc: comment User.java
 * @author Chaisson(chengshengwang)
 * @since May 13, 2011 5:20:38 PM
 * @vision 1.0
 */
public class User {
	
	private int age;
	
	private String name;
	
	private String desc;

	//省去setter/getter
}


还有3个配置文件:
server_insecurity_enc.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=keystorePass
org.apache.ws.security.crypto.merlin.alias.password=apmserverpass
org.apache.ws.security.crypto.merlin.keystore.alias=apmserver
org.apache.ws.security.crypto.merlin.file=serverStore.jks

server_insecurity_sign.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=keystorePass
#org.apache.ws.security.crypto.merlin.alias.password=apmserverpass
org.apache.ws.security.crypto.merlin.keystore.alias=apmserver
org.apache.ws.security.crypto.merlin.file=serverStore.jks


server_outsecurity_enc.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=keystorePass
#org.apache.ws.security.crypto.merlin.alias.password=apmserverpass
#org.apache.ws.security.crypto.merlin.keystore.alias=apmserver
org.apache.ws.security.crypto.merlin.file=serverStore.jks

 

如果我没有看错的话,上面3个配置是一摸一样的,呵呵,仅仅是后面的有部分被注释了。



客户端,我们首先需要一个Test类,TestServiceClient.java

 

package com.db.webservice.test;



import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.db.webservice.TestService;
import com.db.webservice.User;

/**
 * desc: comment TestServiceClient.java
 * @author Chaisson(chengshengwang)
 * @since May 13, 2011 2:17:04 PM
 * @vision 1.0
 */
public class TestServiceClient {

	/**
	 * desc:
	 * @Chaisson(chengshengwang)
	 * @since May 13, 2011
	 * @version 1.0
	 * @param args
	 */
	public static void main(String[] args) {
		/*JaxWsProxyFactoryBean  factory = new JaxWsProxyFactoryBean ();
		factory.setServiceClass(TestService.class);
		factory.setAddress("http://localhost:8080/TestCXF/services/MyService");
		TestService service =(TestService)factory.create();
		service.sayHello("Chaisson");
		
		User user = new User();
		user.setAge(10);
		user.setName("Chaisson");
		user.setDesc("He is a good man");
		System.out.println(service.printMan(user));*/
		
		
		
		ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");   
		TestService client = (TestService)ctx.getBean("client");
		
		User user = new User();
		user.setAge(10);
		user.setName("Chaisson");
		user.setDesc("He is a good man");
		System.out.println(client.printMan(user));

	}

}


当然也需要3个properties配置文件
insecurity_enc.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=keystorePass
org.apache.ws.security.crypto.merlin.alias.password=apmclientpass
org.apache.ws.security.crypto.merlin.keystore.alias=apmclient
org.apache.ws.security.crypto.merlin.file=clientStore.jks

outsecurity_enc.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=keystorePass
org.apache.ws.security.crypto.merlin.alias.password=apmclientpass
org.apache.ws.security.crypto.merlin.keystore.alias=apmclient
org.apache.ws.security.crypto.merlin.file=clientStore.jks

outsecurity_sign.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=keystorePass
org.apache.ws.security.crypto.merlin.alias.password=apmclientpass
org.apache.ws.security.crypto.merlin.keystore.alias=apmclient
org.apache.ws.security.crypto.merlin.file=clientStore.jks

如果我没有看错的话,这回3个配置文件真的一摸一样了。

在客户端还需要加一个服务发布地址的配置文件
serverhost.properties
host.url=http://127.0.0.1:8080/TestCXF/services

最后整个工程少不了Spring配置文件:
applicationContext.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">
	    
    <bean id="client" class="com.db.webservice.TestService" 
         factory-bean="clientFactory" factory-method="create">
    </bean>      
    <bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
         <property name="serviceClass" value="com.db.webservice.TestService" />
		 <property name="address" value="${host.url}/MyService" />
		 <property name="outInterceptors">
			<list>
				<bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />
				<ref bean="wss4jOutConfiguration" />
			</list>
		 </property>
		 <property name="inInterceptors">
			<list>
				<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
				<ref bean="wss4jInConfiguration" />
			</list>
		 </property>
    </bean> 
    	<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:serverhost.properties</value>
			</list>
		</property>
	</bean>
	
	<bean id="wss4jOutConfiguration" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
		<property name="properties">
			<map>
				<entry key="action" value="Timestamp Encrypt Signature" />
				<entry key="user" value="apmclient" />
				<entry key="encryptionUser" value="apmserver" />
				<entry key="signaturePropFile" value="outsecurity_sign.properties" />
				<entry key="signatureKeyIdentifier" value="IssuerSerial" />
				<entry key="encryptionPropFile" value="outsecurity_enc.properties" />
				<entry>
					<key>
						<value>passwordCallbackRef</value>
					</key>
					<ref bean="passwordCallback" />
				</entry>
			</map>
		</property>
	</bean>
	
	<bean id="wss4jInConfiguration" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
		<property name="properties">
			<map>
				<entry key="action" value="Timestamp Encrypt Signature" />
				<entry key="user" value="apmclient" />
				<entry key="decryptionPropFile" value="insecurity_enc.properties" />
				<entry key="enableSignatureConfirmation" value="true" />
				<entry key="signaturePropFile" value="outsecurity_sign.properties" />
				<entry key="signatureKeyIdentifier" value="IssuerSerial" />
				<entry>
					<key>
						<value>passwordCallbackRef</value>
					</key>
					<ref bean="passwordCallback" />
				</entry>
			</map>
		</property>
	</bean>	
	<bean id="passwordCallback" class="com.db.webservice.security.PasswordHandler"/>	
</beans>	


cxf的配置文件:

cxf-servlet.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: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" /> 
    
	<bean id="testService" class="com.db.webservice.impl.TestServiceImpl" /> 
	<jaxws:endpoint id="myService" implementor="#testService" address="/MyService" >
		<jaxws:inInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/> 
			<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor"/>
			<ref bean="wss4jInConfiguration"/>
		</jaxws:inInterceptors>
		<jaxws:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
			<bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor"/>
			<ref bean="wss4jOutConfiguration"/>
		</jaxws:outInterceptors>
	</jaxws:endpoint> 
	
	<!-- 
	<bean id="client" class="com.db.webservice.TestService" 
         factory-bean="clientFactory" factory-method="create"/> 
    <bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
         <property name="serviceClass" value="com.db.webservice.TestService"/>
         <property name="address" value="http://localhost:8080/TestCXF/services/MyService"/>
    </bean>	 -->   
    
    
    <bean id="wss4jInConfiguration" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
		<property name="properties">
			<map>
				<entry key="action" value="Timestamp Encrypt Signature"/>
				<entry key="decryptionPropFile" value="server_insecurity_enc.properties"/>
				<entry key="signaturePropFile" value="server_insecurity_sign.properties"/>
				<entry>
					<key>
						<value>passwordCallbackRef</value>
					</key>
					<ref bean="passwordCallback"/>
				</entry>
			</map>
		</property>
	</bean>
	
	<bean id="wss4jOutConfiguration" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
		<property name="properties">
			<map>
				<entry key="action" value="Timestamp Encrypt Signature"/>
				<entry key="user" value="apmserver" />
				<entry key="encryptionUser" value="apmclient" />
				<entry key="encryptionPropFile" value="server_outsecurity_enc.properties"/>
				<entry key="signaturePropFile" value="server_insecurity_sign.properties"/>
				<entry>
					<key>
						<value>passwordCallbackRef</value>
					</key>
					<ref bean="passwordCallback"/>
				</entry>
			</map>
		</property>
	</bean>
     
    <bean id="passwordCallback" class="com.db.webservice.security.PasswordHandler"/> 
</beans>


还有一个web.xml,也简单提一下吧

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" 
	xmlns="http://java.sun.com/xml/ns/j2ee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	
  <servlet>
  	<description>cxfServlet</description>
    <display-name>cxfServlet</display-name>
    <servlet-name>cxfServlet</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <load-on-startup>2</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/cxf-servlet.xml</param-value>  
  </context-param>  
  <listener>  
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  </listener>  
  
	
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>


好了,project搭建完毕,在最后进行测试的时候,发生了很多问题。
如果你从http://download.youkuaiyun.com/source/3539244位置下载了我的demo,直接run的话,也是会报异常的。因为某些国家对加密算法有一定限制,所以SUN公司在发布JDK的时候里面的策略文件是限制版的。需要到SUN公司网站上下载非限制版的策略文件进行替换。
解决的办法,请参考我的另一篇文章http://blog.youkuaiyun.com/wangchsh2008/article/details/6708718

本文参考了以下一些文章:

 

http://blog.youkuaiyun.com/kunshan_shenbin/article/details/3813000
http://blog.myspace.cn/e/403951535.htm 
http://cxf.apache.org/docs/ws-security.html

 

 

 

 

 

 

 

 

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值