使用 Spring4 + CXF3 + WS-Security 开发 WebService
来源:互联网 发布:golang test用法 编辑:程序博客网 时间:2024/05/20 02:54
说明
目前运维的平台需要和其他类型相似的平台进行数据对接,由于数据量不大,基本上不存在对接时的性能问题,所以选择使用 WebService 进行数据对接。综合来看,Spring + CXF 的技术实现方式是现在的主流,有较多的技术支持,因此选择这种方式来开发 WebService。
(一)开发环境
选用 JDK1.7 + Maven3.3.9 + Spring4.3.10 + CXF3.1.12 + Tomcat7.0 来进行开发。
(二) WebService 服务端开发
1. 新建 Maven 项目,添加 Maven 依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version></dependency><!--cxf--><dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.1.12</version></dependency><dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.1.12</version></dependency><dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-security</artifactId> <version>3.1.12</version></dependency><!--spring--><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.10.RELEASE</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.3.10.RELEASE</version></dependency>
2. 创建一个 JAX-WS 注解的 Service类
定义一个接口及其实现类
package demo.ws.soap_spring_cxf;import javax.jws.WebParam;import javax.jws.WebService;@WebServicepublic interface HelloService { String say(@WebParam(name = "name") String name);}
package demo.ws.soap_spring_cxf;import org.springframework.stereotype.Component;import javax.jws.WebService;@WebService@Componentpublic class HelloServiceImpl implements HelloService { public String say(String name) { return "hello " + name; }}
定义好 WebService 服务类,再进行适当的配置,就可以准备发布服务了。
3. 配置 CXF 与 Spring
在发布服务之前,我们需要在 web.xml 中配置监听器,加载 Spring 配置文件;添加 CXF Servlet,处理请求
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>webserviceDemo</display-name> <!--spring--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> <!--cxf--> <servlet> <servlet-name>cxf</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>cxf</servlet-name> <url-pattern>/ws/*</url-pattern> </servlet-mapping></web-app>
spring.xml 中<context:component-scan>配置需要扫描的包,同时使用 <import> 标签引入其他配置文件
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="demo.ws"/> <import resource="spring-cxf.xml"/> <import resource="spring-beans.xml"/></beans>
spring-cxf.xml 用于发布服务,spring-beans.xml 用于配置验签信息
4. 使用 CXF’s XML 发布服务
使用 XML 发布服务,在 spring-cxf.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: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 http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd"> <!--发布服务--> <jaxws:endpoint id="helloService" implementor="demo.ws.soap_spring_cxf.HelloServiceImpl" address="/soap/hello"> </jaxws:endpoint> <cxf:bus> <cxf:features> <cxf:logging/> </cxf:features> </cxf:bus></beans>
<cxf:logging> 标签用于开启服务日志。启动服务后,访问 http://localhost:8080/ws/soap/hello?wsdl ,显示如下信息即表示服务发布成功。
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://soap_spring_cxf.ws.demo/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="HelloServiceImplService" targetNamespace="http://soap_spring_cxf.ws.demo/"> ... <wsdl:message name="say"> <wsdl:part element="tns:say" name="parameters"/> </wsdl:message> <wsdl:message name="sayResponse"> <wsdl:part element="tns:sayResponse" name="parameters"/> </wsdl:message> <wsdl:portType name="HelloService"> <wsdl:operation name="say"> <wsdl:input message="tns:say" name="say"/> <wsdl:output message="tns:sayResponse" name="sayResponse"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="HelloServiceImplServiceSoapBinding" type="tns:HelloService"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="say"> <soap:operation soapAction="" style="document"/> <wsdl:input name="say"> <soap:body use="literal"/> </wsdl:input> <wsdl:output name="sayResponse"> <soap:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="HelloServiceImplService"> <wsdl:port binding="tns:HelloServiceImplServiceSoapBinding" name="HelloServiceImplPort"> <soap:address location="http://localhost:8080/ws/soap/hello"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
(三)完善 WebService ,使用签名和加密
在数据传输过程中,往往会有加密的需求,这里使用 WS-Security 来实现签名和加密,它提供了 WSS4J interceptors 。而在 CXF2.2 之后可以使用 WS-SecurityPolicy,这种方式更简单也更标准。
1. 生成公钥和私钥
在配置拦截器之前,需要生成公钥和私钥,用于签名加密以及验签解密。CXF 官方文档中使用 X.509 证书来生成公钥和私钥,但是官方文档提示说这种方式不适合生产环境。
keytool -genkey -alias ciecc -keypass cieccPassword -keyalg RSA -keysize 1024 -validity 3650 -keystore privatestore.jks -storepass keyStorePassword -dname "cn=ciecc" keytool -selfcert -alias ciecc -keystore privatestore.jks -storepass keyStorePassword -keypass cieccPasswordkeytool -importkeystore -alias ciecc -deststorepass keyStorePassword -destkeypass cieccPassword -destkeystore publicstore.jks -srckeystore privatestore.jks -srcstorepass keyStorePassword
keytool 是 JDK 自带的命令,ciecc 是私钥的别名,cieccPassword 是私钥的密码,keyStorePassword 是秘钥库的密码。keytool 具体使用方法可以自行查找。使用以上命令会在当前目录下生成 publicstore.jks 和 privatestore.jks 两个文件,公钥和私钥分别保存在这两个文件中。
2. 配置拦截器(Interceptor)
对于服务端来说,需要在 spring-cxf.xml 中配置 WSS4JInInterceptor,对接收到的信息进行验签并解密。对于客户端的话,配置 WSS4JOutInterceptor,对要发送的请求进行签名和加密。这里是从服务端的角度来配置的,客户端的配置会在后面讲到。
<bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"> <constructor-arg> <map> <entry key="action" value="Signature Encrypt"/> <!-- 提供公钥的密码 --> <entry key="passwordCallbackRef" value-ref="serverPasswordCallback"/> <entry key="signatureVerificationPropFile" value="server.properties"/> <entry key="decryptionPropFile" value="server.properties"/> </map> </constructor-arg></bean>
其中 signatureVerificationPropFile 和 decryptionPropFile 属性的配置信息都来自 server.properties 文件,这个配置文件中的内容如下:
org.apache.wss4j.crypto.provider=org.apache.wss4j.common.crypto.Merlinorg.apache.wss4j.crypto.merlin.keystore.type=jksorg.apache.wss4j.crypto.merlin.keystore.password=keyStorePasswordorg.apache.wss4j.crypto.merlin.keystore.file=publicstore.jks
而 passwordCallbackRef 这个 bean 根据签名用户提供对应的密钥密码,代码如下:
package demo.ws.soap_spring_cxf_wss4j;import org.apache.wss4j.common.ext.WSPasswordCallback;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import javax.security.auth.callback.Callback;import javax.security.auth.callback.CallbackHandler;import javax.security.auth.callback.UnsupportedCallbackException;import java.io.IOException;@Componentpublic class ServerPasswordCallback implements CallbackHandler { @Autowired private SignatureUser user; public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { WSPasswordCallback callback = (WSPasswordCallback) callbacks[0]; String clientUsername = callback.getIdentifier(); String serverPassword = user.getUserMap().get(clientUsername); if (serverPassword != null) { callback.setPassword(serverPassword); } }}
SignatureUser 类提供了用户及密码信息
package demo.ws.soap_spring_cxf_wss4j;import org.springframework.stereotype.Component;import java.util.Map;@Componentpublic class SignatureUser { private Map<String, String> userMap; public Map<String, String> getUserMap() { return userMap; } public void setUserMap(Map<String, String> userMap) { this.userMap = userMap; }}
密码信息通过 Spring 注入,即 spring-beans.xml 文件中的 signatureUser bean
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="signatureUser" class="demo.ws.soap_spring_cxf_wss4j.SignatureUser"> <property name="userMap"> <map> <entry key="ciecc" value="cieccPassword"/> </map> </property> </bean></beans>
3. 签名和加密
完成以上工作后,对数据进行验签只需要在发布服务的时候添加 inInterceptors 即可
<!--发布服务--><jaxws:endpoint id="helloService" implementor="demo.ws.soap_spring_cxf.HelloServiceImpl" address="/soap/hello"> <jaxws:inInterceptors> <ref bean="wss4jInInterceptor"/> </jaxws:inInterceptors></jaxws:endpoint>
(四)生成客户端进行测试
1. 生成客户端
使用 Java 自带的 wsimport 命令可以很方便地根据 wsdl 文档生成客户端。
wsimport -s . -encoding utf-8 http://localhost:8080/ws/soap/hello?wsdl
执行命令,会在当前目录下生成客户端源码。下一步是配置客户端调用服务
2. 调用服务
新建 Maven 项目,添加和服务端相同的 Maven 依赖,将客户端源码导入到 src 目录下,配置 spring-client.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:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="ws"/> <jaxws:client id="helloService" serviceClass="demo.ws.soap_spring_cxf.HelloServiceImplService" address="http://localhost:8080/ws/soap/hello?wsdl"> <jaxws:outInterceptors> <ref bean="wss4jOutInterceptor"/> </jaxws:outInterceptors> </jaxws:client> <!--客户端发送请求拦截器--> <bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"> <constructor-arg> <map> <!--签名(使用自己的私钥)--> <entry key="action" value="Signature Encrypt"/> <entry key="user" value="ciecc"/> <entry key="passwordCallbackClass" value="ws.ClientPasswordCallback"/> <entry key="signaturePropFile" value="client.properties"/> <!-- 加密 --> <entry key="encryptionPropFile" value="client.properties"/> </map> </constructor-arg> </bean></beans>
上面的配置方式基本上和服务端一致,只不过用到的是 WSS4JOutInterceptor,其中 user 的值必须是私钥的别名,即 ciecc,passwordCallbackClass 的类使用回调方法提供私钥密码来使用私钥进行签名。
package ws;import org.apache.wss4j.common.ext.WSPasswordCallback;import org.springframework.stereotype.Component;import javax.security.auth.callback.Callback;import javax.security.auth.callback.CallbackHandler;import javax.security.auth.callback.UnsupportedCallbackException;import java.io.IOException;@Componentpublic class ClientPasswordCallback implements CallbackHandler { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { WSPasswordCallback callback = (WSPasswordCallback) callbacks[0]; callback.setPassword("cieccPassword"); //key 的密码 }}
signaturePropFile 和 encryptionPropFile 所需要的签名属性文件和加密属性文件都是 client.properties,其配置如下:
org.apache.wss4j.crypto.provider=org.apache.wss4j.common.crypto.Merlinorg.apache.wss4j.crypto.merlin.keystore.type=jksorg.apache.wss4j.crypto.merlin.keystore.password=keyStorePasswordorg.apache.wss4j.crypto.merlin.keystore.alias=cieccorg.apache.wss4j.crypto.merlin.keystore.file=privatestore.jks
3. 测试
使用 Junit 测试客户端签名加密及服务端验签解密
package ws;import demo.ws.soap_spring_cxf.HelloService;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Client { @Test public void testHello() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-client.xml"); HelloService service = context.getBean("helloService", HelloService.class); String result = service.say("world"); System.out.println(result); }}
客户端调用服务,发送 “word”,服务端接收到如下信息:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1"> <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="EK-d18f673f-6ecc-4a2c-a21d-570bb1effcd6"> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <wsse:SecurityTokenReference> <ds:X509Data> <ds:X509IssuerSerial> <ds:X509IssuerName>CN=ciecc</ds:X509IssuerName> <ds:X509SerialNumber>67874909</ds:X509SerialNumber> </ds:X509IssuerSerial> </ds:X509Data> </wsse:SecurityTokenReference> </ds:KeyInfo> <xenc:CipherData> <xenc:CipherValue>xE9E5Z6CwRfcovG8RI8qUVtdUe/jSJwsUORC8Q3EkOmBVSXt1/+YZI7XuXcTYcFfREKyKynxSSrC0lCk7O0/0yQU56SS+E7tWfxYzxRyJANE7w9EMEksFm4J7REbYo0kpvfjFk4guudYn8T3VHO372BR9etAEanD1CK8jABUxrk=</xenc:CipherValue> </xenc:CipherData> <xenc:ReferenceList> <xenc:DataReference URI="#ED-3a49d5cb-149d-4424-beaa-1170c25ee7b9"/> </xenc:ReferenceList> </xenc:EncryptedKey> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="SIG-21b77015-5a76-4bac-87c2-51a424195467"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap"></ec:InclusiveNamespaces> </ds:CanonicalizationMethod> <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> <ds:Reference URI="#id-4e904297-6fbd-4218-813b-1c15756111f9"> <ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> </ds:Transforms> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>2IVsqcAWoiKks6D7+SgN5dE+oi0=</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>zKeU6h2BPznK/YokHZrLafToI8A5cp2Nzgx89ZfgdBYmeGQtF/7Dy7BridVS00ftNPHp5OdrWRQ9504J//qOUzYAam0V+5CjyMBwldbRL1gG/57jlOxg+prsIotEmHg4Zo+KcK0vHO5Q+zrK3k/kDehHJ3XML6w/MbEEsOWnq2U=</ds:SignatureValue> <ds:KeyInfo Id="KI-678fba15-f90f-4215-98c4-e33c2d06c93c"> <wsse:SecurityTokenReference wsu:Id="STR-ca6c19eb-7eed-476c-9867-f8a27c05241a"> <ds:X509Data> <ds:X509IssuerSerial> <ds:X509IssuerName>CN=ciecc</ds:X509IssuerName> <ds:X509SerialNumber>67874909</ds:X509SerialNumber> </ds:X509IssuerSerial> </ds:X509Data> </wsse:SecurityTokenReference> </ds:KeyInfo> </ds:Signature> </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-4e904297-6fbd-4218-813b-1c15756111f9"> <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Id="ED-3a49d5cb-149d-4424-beaa-1170c25ee7b9" Type="http://www.w3.org/2001/04/xmlenc#Content"> <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-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" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" wsse11:TokenType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey"> <wsse:Reference URI="#EK-d18f673f-6ecc-4a2c-a21d-570bb1effcd6"/> </wsse:SecurityTokenReference> </ds:KeyInfo> <xenc:CipherData> <xenc:CipherValue>1by/ysWb1wtB4i+HdqzvAJalQwZNeSn50+Z1SH+s/wfAe14TpJANAqukPFPn3gP3hi2K85nhCNuS+BMtJGawpm/LgtkxGaXQAenqMTCygzMhv58yvQ0jxnCoI9yxJn0AALHt3C0vfVaBWURoBNoX3Q==</xenc:CipherValue> </xenc:CipherData> </xenc:EncryptedData> </soap:Body></soap:Envelope>
可见客户端发送的信息都经过了加密。服务端接收信息后,能成功返回“hello world”则表明服务端成功验签解密并提供了服务。
参考文档:
CXF User’s Guide:http://cxf.apache.org/docs/index.html
CXF WS-Security:http://cxf.apache.org/docs/ws-security.html
《架构探险 从零开始写javaweb框架》:https://my.oschina.net/huangyong/blog
- 使用 Spring4 + CXF3 + WS-Security 开发 WebService
- WebService Spring4.3+cxf3.1.5整合Demo
- 利用cxf3.14+spring4.2发布webservice
- 使用spring-ws开发webservice
- 使用spring-ws开发webservice
- 使用JAX-WS开发WebService
- cxf3.14+spring4.2以rest风格发布webservice
- XFire webservice WS-Security安全
- 使用JAX-WS开发简单webservice
- 使用jax-ws开发webservice(二)
- WebService笔记(第二弹:使用JAX-WS开发WebService)
- WebService -- 在AXIS下实现WS-Security
- webservice安全之WS-Security验证
- webservice cxf+spring+WS-security配置示例
- WebService加入WS-Security的应用
- spring4.1.6+cxf3.0.8的简单WebService案例(maven工程)
- maven3.1.1 spring4.3.4+mybatis3.2.8+CXF3.1.7 发布webservice 注意几点
- 使用JAX-WS开发WebService简单入门(1)
- Mysql数据库基础:安装-练习
- (冒泡,选择,插入,归并,堆排)排序算法
- sap 中功能模块参数后台表FUPARAREF
- php字符串截取的简单方法
- C++之实现大顶堆(1)---《那些奇怪的算法》
- 使用 Spring4 + CXF3 + WS-Security 开发 WebService
- iOS开发笔记-升级Xcode9和升级iOS11后踩坑记
- Python元类
- 常考sql
- MTP/MPO光纤跳线布线解决方案
- mysql下的user表为空---mysql错误
- curl访问https网站时,先导入certificate,然后再访问
- 创建数据表和修改数据表
- js Set类型