WS-Security(Web服务安全)是一种提供在Web Service上应用安全的方法的网络传输协议,协议包含了关于如何在Web Service消息上保证完整性和机密性的规约。WS-Security描述了如何将签名和加密头加入SOAP消息。除此以外,还描述了如何在消息中加入安全令牌,包括二进制安全令牌,如X.509认证证书和Kerberos门票(ticket)。WS-Security将安全特性放入SOAP消息头中在应用层处理,这样协议保证了端到端的安全。
在CXF中实现服务端或者客户端的WS-Security,需要设置WSS4J拦截器。WS-Security规范支持多种令牌方式,UserNameToken头是其中一种方式。UserNameToken头是一种把用户米和密码或者密码摘要传到另一个端点的标准方式。
在配置文件pom.xml中添加WS-Security引用。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rvho</groupId>
<artifactId>cxfservers</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<!-- CXF版本 -->
<cxf.version>3.1.1</cxf.version>
<!-- Spring版本 -->
<spring.version>4.1.7.RELEASE</spring.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- End Spring -->
<!-- CXF -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-ws-security</artifactId>
<version>${cxf.version}</version>
</dependency>
<!--如果用tomcat发布,不需要引用jetty-->
<!-- <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>${cxf.version}</version> </dependency> -->
<!-- End CXF -->
</dependencies>
</project>
服务端回调函数ServerPasswordCallbackHandler,校验客户端请求是否合法,合法就放行,否则拒绝执行任何操作。
package com.rvho.cxfserver.callback;
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.wss4j.common.ext.WSPasswordCallback;
public class ServerPasswordCallbackHandler implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
if (pc.getIdentifier().equals("admin")) {
//设置密码
//这个密码和客户端发送的密码进行比较
//如果和客户端不同将抛出org.apache.ws.security.WSSecurityException
pc.setPassword("123");
}
}
}
服务端通过输入拦截器WSS4JInInterceptor实现WS-Security的校验,如果服务端集成了Spring,WSS4JInInterceptor配置如下。
<?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:soap="http://cxf.apache.org/bindings/soap" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<!-- 回调函数 -->
<bean id="serverPasswordCallback" class="com.rvho.cxfserver.callback.ServerPasswordCallbackHandler"></bean>
<jaxws:endpoint id="helloWSEndpoint" implementor="#helloWS" address="/hello">
<jaxws:inInterceptors>
<!-- WS-Security拦截器 -->
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<!-- 拦截器的构造函数参数 -->
<constructor-arg>
<map>
<entry key="action" value="UsernameToken"/>
<!-- 密码类型,PasswordText表示明文 -->
<entry key="passwordType" value="PasswordText"/>
<entry key="passwordCallbackRef">
<!-- 回调函数引用 -->
<ref bean="serverPasswordCallback"/>
</entry>
</map>
</constructor-arg>
</bean>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
</jaxws:outInterceptors>
</jaxws:endpoint>
</beans>
如果服务端通过代码方式发布服务,可以用API添加。
JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
factory.setServiceClass(HelloWS.class);
factory.setAddress("http://localhost:8280/cxfservers/services/hello");
factory.setServiceBean(new HelloWSImpl());
//WS-Security输入拦截器
Map<String, Object> inProps = new HashMap<String, Object>();
inProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
inProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
inProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ServerPasswordCallbackHandler.class.getName());
factory.getInInterceptors().add(new WSS4JInInterceptor(inProps));
factory.getInInterceptors().add(new LoggingInInterceptor());
factory.create();
客户端回调函数ServerPasswordCallbackHandler,回调函数在发请求时添加密码。
package com.rvho.cxfclient.callback;
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.wss4j.common.ext.WSPasswordCallback;
public class ClientPasswordCallbackHandler implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
pc.setPassword("123");
}
}
客户端请求
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(HelloWS.class);
factory.setAddress("http://localhost:8280/cxfservers/services/hello");
factory.getInInterceptors().add(new org.apache.cxf.interceptor.LoggingInInterceptor());
//WS-Security输出拦截器
Map<String, Object> outProps = new HashMap<String, Object>();
outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
//添加用户名
outProps.put(WSHandlerConstants.USER, "admin");
outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ClientPasswordCallbackHandler.class.getName());
factory.getOutInterceptors().add(new WSS4JOutInterceptor(outProps));
factory.getOutInterceptors().add(new com.rvho.cxfclient.interceptor.AuthAddInterceptor());
factory.getOutInterceptors().add(new org.apache.cxf.interceptor.LoggingOutInterceptor());
HelloWS helloWS = factory.create(HelloWS.class);
String welcome = helloWS.welcome("[email protected]");
System.out.println(welcome);
八月 03, 2015 2:31:51 下午 org.apache.cxf.services.HelloWSService.HelloWSPort.HelloWS
信息: Outbound Message
---------------------------
ID: 1
Address: http://localhost:8280/cxfservers/services/hello
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: <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"><wsse:UsernameToken wsu:Id="UsernameToken-581191d1-7dcb-479b-a739-0ebb063d740f"><wsse:Username>admin</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">123</wsse:Password></wsse:UsernameToken></wsse:Security><auth xmlns="http://www.tmp.com/auth"><name>admin</name><password>admin</password></auth></soap:Header><soap:Body><ns2:welcome xmlns:ns2="http://www.tmp.com/services/hello"><name>[email protected]</name></ns2:welcome></soap:Body></soap:Envelope>
--------------------------------------
八月 03, 2015 2:31:51 下午 org.apache.cxf.services.HelloWSService.HelloWSPort.HelloWS
信息: Inbound Message
----------------------------
ID: 1
Address: http://localhost:8280/cxfservers/services/hello
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=UTF-8
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], Content-Length=[847], content-type=[text/xml; charset=UTF-8], Host=[localhost:8280], Pragma=[no-cache], SOAPAction=[""], User-Agent=[Apache CXF 3.1.1]}
Payload: <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"><wsse:UsernameToken wsu:Id="UsernameToken-581191d1-7dcb-479b-a739-0ebb063d740f"><wsse:Username>admin</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">123</wsse:Password></wsse:UsernameToken></wsse:Security><auth xmlns="http://www.tmp.com/auth"><name>admin</name><password>admin</password></auth></soap:Header><soap:Body><ns2:welcome xmlns:ns2="http://www.tmp.com/services/hello"><name>[email protected]</name></ns2:welcome></soap:Body></soap:Envelope>
--------------------------------------