- 数字证书的准备
下面做的服务端和客户端证书在例子中无法加解密,不知道什么原因,我是使用正式环境中的客户端和服务端进行开发测试的,所以需要大家自己去准备证书,或者有人知道为什么jdk生成的一对证书无法加解密的原因那在好不过了。(例子中客户端和服务端都放在一起项目中,大家自己分开开发测试即可)下面是我用jdk生成的证书:
1.1、客户端
1、创建私钥和keystore
keytool -genkey -alias clientprivatekey -keypass keypass -keystore client.jks -storepass storepass -dname "CN=zs.com,C=CN" -keyalg RSA
2、自签名
keytool -selfcert -keystore client.jks -storepass storepass -alias clientprivatekey -keypass keypass
3、导出公钥到rsa
keytool -export -alias clientprivatekey -file client.rsa -keystore client.jks -storepass storepass
1.2、服务端
1、创建私钥和keystore
keytool -genkey -alias serverprivatekey -keypass keypass -keystore server.jks -storepass storepass -dname "CN=zs.com,C=CN" -keyalg RSA
2、自签名
keytool -selfcert -keystore server.jks -storepass storepass -alias serverprivatekey -keypass keypass
3、导出公钥到rsa
keytool -export -alias serverprivatekey -file server.rsa -keystore server.jks -storepass storepass
1.3、将服务端公钥加入客户端jks中
keytool -import -alias serverprivatekey -file server.rsa -keystore client.jks -storepass storepass
1.4、将客户端公钥加入服务端jks中
keytool -import -alias clientprivatekey -file client.rsa -keystore server.jks -storepass storepass
- 证书配置文件
2.1、客户端配置
jks文件放在ca包下,ca包直接在项目的src目录下,配置文件名称为client.properties(放在ca下),注意等号两边不要出现空格
1 org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin 2 org.apache.ws.security.crypto.merlin.keystore.type=jks 3 org.apache.ws.security.crypto.merlin.keystore.password=证书的storepass 4 org.apache.ws.security.crypto.merlin.keystore.alias=证书的别名 5 org.apache.ws.security.crypto.merlin.file=ca/client.jks
2.2、服务端配置
jks文件放在ca包下,ca包直接在项目的src目录下,配置文件名称为server.properties(放在ca下),注意等号两边不要出现空格
1 org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin 2 org.apache.ws.security.crypto.merlin.keystore.type=jks 3 org.apache.ws.security.crypto.merlin.keystore.password=storepass 4 org.apache.ws.security.crypto.merlin.keystore.alias=serverprivatekey 5 org.apache.ws.security.crypto.merlin.file=ca/server.jks
- 秘钥配置类
3.1、客户端
1 package cxf.test.client; 2 3 import java.io.IOException; 4 import javax.security.auth.callback.Callback; 5 import javax.security.auth.callback.CallbackHandler; 6 import javax.security.auth.callback.UnsupportedCallbackException; 7 import org.apache.ws.security.WSPasswordCallback; 8 9 public class UTPasswordClientCallBack implements CallbackHandler { 10 11 @Override 12 public void handle(Callback[] callbacks) throws IOException,UnsupportedCallbackException { 13 WSPasswordCallback pc = (WSPasswordCallback) callbacks[0]; 14 pc.setPassword("证书的keypass"); 15 System.out.println("Client Identifier=" + pc.getIdentifier()); 16 System.out.println("Client Password=" + pc.getPassword()); 17 } 18 19 }
3.2、服务端
1 package cxf.test.server; 2 3 import java.io.IOException; 4 5 import javax.security.auth.callback.Callback; 6 import javax.security.auth.callback.CallbackHandler; 7 import javax.security.auth.callback.UnsupportedCallbackException; 8 9 import org.apache.ws.security.WSPasswordCallback; 10 11 public class UTPasswordServerCallBack implements CallbackHandler { 12 13 @Override 14 public void handle(Callback[] callbacks) throws IOException,UnsupportedCallbackException { 15 WSPasswordCallback pc = (WSPasswordCallback) callbacks[0]; 16 pc.setPassword("证书的keypass"); 17 System.out.println("Client Identifier=" + pc.getIdentifier()); 18 System.out.println("Client Password=" + pc.getPassword()); 19 } 20 21 }
- 配置文件
4.1、web.xml配置
1 <context-param> 2 <param-name>contextConfigLocation</param-name> 3 <param-value>WEB-INF/beans.xml</param-value><!-- spring配置文件 --> 4 </context-param> 5 <listener> 6 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 7 </listener> 8 <servlet> 9 <servlet-name>CXFServlet</servlet-name> 10 <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> 11 </servlet> 12 <servlet-mapping> 13 <servlet-name>CXFServlet</servlet-name> 14 <url-pattern>/services/*</url-pattern><!-- 匹配services/*后缀url --> 15 </servlet-mapping>
4.2、beans.xml配置(spring配置文件)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" 4 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"> 5 <!-- jar包中自带的cxf文件夹下的*.xml文件 --> 6 <import resource="classpath:META-INF/cxf/cxf.xml" /> 7 <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" /> 8 <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> 9 <!-- 配置服务端方法所在的类及数字签名 --> 10 <jaxws:endpoint id="TestWebService" implementor="cxf.test.server.TestWebServiceImpl" 11 address="/wstest"> 12 <!-- 通过拦截器对客户端发送的数据和签名进行解密处理 --> 13 <jaxws:inInterceptors> 14 <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"> 15 <constructor-arg> 16 <map> 17 <entry key="action" value="Signature Encrypt" /> 18 <entry key="signaturePropFile" value="ca/server.properties" /> 19 <entry key="decryptionPropFile" value="ca/server.properties" /> 20 <entry key="passwordCallbackClass" value="cxf.test.server.UTPasswordServerCallBack" /> 21 </map> 22 </constructor-arg> 23 </bean> 24 </jaxws:inInterceptors> 25 <!-- 通过拦截器将服务端发送给客户端的数据和签名进行加密 --> 26 <jaxws:outInterceptors> 27 <bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"> 28 <constructor-arg> 29 <map> 30 <entry key="action" value="Signature Encrypt" /> 31 <entry key="user" value="服务端证书别名" /> 32 <entry key="signatureUser" value="服务端证书别名" /> 33 <entry key="signaturePropFile" value="ca/server.properties" /> 34 <entry key="encryptionUser" value="useReqSigCert"></entry> 35 <entry key="decryptionPropFile" value="ca/server.properties" /> 36 <entry key="passwordCallbackClass" value="cxf.test.server.UTPasswordServerCallBack" /> 37 </map> 38 </constructor-arg> 39 <property name="allowMTOM" value="true"></property> 40 </bean> 41 </jaxws:outInterceptors> 42 </jaxws:endpoint> 43 </beans>
- 服务端类
5.1、接口类
1 package cxf.test.server; 2 3 import javax.jws.WebParam; 4 import javax.jws.WebService; 5 6 //配置targetNamespace,不进行配置,将使用当前类所在的包名顺序反向(如果这里没设置,默认命名空间为:http://server.test.cxf/)为命名空间。 7 //不进行命名空间设置,会存在客户端和服务端需要保持包名一致的问题,这在现实开发中肯定是很麻烦的事 8 @WebService(name = "TestWebService", targetNamespace = "http://cxf.test.tns/") 9 public interface TestWebService { 10 // 为了更好的兼容不同的开发语言、jdk版本等,接口方法的参数和返回结果最好是字符串,对象按xml格式进行编写和解析 11 // 接口方法的参数最好使用annotation设定接口方法的参数名,不进行设置,默认会在参数前加上“{”等符号(具体符号可能有误), 12 // 这样可能会给客户端调用带来不必要的麻烦,cxf有好几个annotation配置,如果客户端和服务端传递数据复杂类型参数时出现null等情况,一般进行相应annotation配置即可 13 public abstract String testMethod(@WebParam(name = "xml") String xml); 14 }
5.2、接口实现类
1 package cxf.test.server; 2 3 4 public class TestWebServiceImpl implements TestWebService { 5 public String testMethod(String xml) { 6 System.out.println(xml); 7 return "服务端被调用"; 8 } 9 }
- 客户端类
6.1、接口类
1 package cxf.test.client; 2 3 import javax.jws.WebParam; 4 import javax.jws.WebService; 5 6 //使用annotation设置接口的命名空间。如果不进行设置,会出现客户端和服务端的接口类和bean类所在包名需保持一致的问题。出现对象作为参数 7 //或返回结果时为null的情况,不进行设置,默认按包名的反向顺序(如果该接口方法不进行设置,默认命名空间为:http://server.test.cxf/)作为命名空间 8 //最好是设置命名空间,包名暴露也不是好事 9 @WebService(name = "TestWebService", targetNamespace = "http://cxf.test.tns/") 10 public interface TestWebService { 11 // 为了更好的兼容不同的开发语言、jdk版本等,接口方法的参数和返回结果最好是字符串,对象按xml格式进行编写和解析 12 // 接口方法的参数最好使用annotation设定接口方法的参数名,不进行设置,默认会在参数前加上“{”等符号(具体符号可能有误), 13 // 这样可能会给客户端调用带来不必要的麻烦 14 public abstract String testMethod(@WebParam(name = "xml") String xml); 15 }
6.2、公共方法
1 package cxf.test.client; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; 6 import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor; 7 import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor; 8 9 /** 10 * 客户端调用服务端公共类 11 * 12 * @author yu 13 * 14 */ 15 public class Client { 16 /** 17 * 调用服务端接口时,先调用该方法,获得服务端接口方法,该方法设置数字签名的加解密信息 18 * 19 * @param address 20 * wsdl地址,包含“?wsdl”后缀 21 * @return 22 */ 23 public TestWebService call(String address) throws Exception { 24 JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); 25 factory.setServiceClass(TestWebService.class); 26 factory.setAddress(address); 27 Map<String, Object> properties = new HashMap<String, Object>(); 28 properties.put("mtom-enabled", Boolean.TRUE); 29 factory.setProperties(properties); 30 // 客户端请求服务端时,客户端进行签名和加密,对应服务端请求拦截器。 31 Map<String, Object> outProps = new HashMap<String, Object>(); 32 outProps.put("action", "Signature Encrypt"); 33 outProps.put("user", "85e46503a2958c9137b832ebca4ee304_e9c397b2-2e69-4de0-8183-e978aa23b1d5");// 客户端证书别名 34 outProps.put("passwordCallbackClass", 35 UTPasswordClientCallBack.class.getName()); 36 outProps.put("encryptionUser", "9598de1694450bdbe56ff58d7fa55cb9_e9c397b2-2e69-4de0-8183-e978aa23b1d5");// 服务端证书别名 37 outProps.put("encryptionPropFile", "ca/client.properties");// 客户端证书配置信息 38 outProps.put("signatureUser", "85e46503a2958c9137b832ebca4ee304_e9c397b2-2e69-4de0-8183-e978aa23b1d5");// 客户端证书别名 39 outProps.put("signaturePropFile", "ca/client.properties"); 40 WSS4JOutInterceptor outInterceptor = new WSS4JOutInterceptor(outProps); 41 outInterceptor.setAllowMTOM(true); 42 factory.getOutInterceptors().add(outInterceptor); 43 // 服务端响应客户端请求时,客户端对服务端签名和加密进行处理,对应服务端响应拦截器 44 Map<String, Object> inProps = new HashMap<String, Object>(); 45 inProps.put("action", "Signature Encrypt"); 46 inProps.put("passwordCallbackClass", 47 UTPasswordClientCallBack.class.getName()); 48 inProps.put("decryptionPropFile", "ca/client.properties"); 49 inProps.put("signaturePropFile", "ca/client.properties"); 50 factory.getInInterceptors().add(new WSS4JInInterceptor(inProps)); 51 TestWebService webService = (TestWebService) factory.create(); 52 return webService; 53 } 54 55 }
6.3、测试
1 package cxf.test.client; 2 3 public class TestClient { 4 // 服务端wsdl地址,包含"?wsdl"后缀,wsdl地址一般放在数据库等地方,方便服务端改动时,客户端好更改. 5 private static String address = "http://127.0.0.1:7001/cxfserver/services/wstest?wsdl"; 6 7 public static void main(String[] args) { 8 try { 9 Client client = new Client(); 10 TestWebService webService = client.call(address); 11 String result = webService.testMethod("客户端调用服务端"); 12 System.out.println(result); 13 } catch (Exception e) { 14 e.printStackTrace(); 15 } 16 } 17 }
- 说明
如果webservice的方法参数和返回值是xml(即将对象编写成xml作为参数传递),可以使用JiBx对对象和xml之间的转换做操作,可以参考下面文章:
http://www.ibm.com/developerworks/cn/java/tutorials/j-jibx1/