天天看点

CXF之九 WS-Security

Webservice 的安全

Webservice为作为方便的服务被用广大领域使用的同时,也成为了黑客们的美食。在这里,本文将就目前对Webservice安全所能做的改进做简单介绍。

在Webservice中的安全主要分为以下三个方面。

传输 SSL/HTTPS 对连接加密,而不是传输数据

消息 数据加密(XML Encryption) 数字签名(XML-DSIG)

底层架构 利用应用服务安全机制

传输时的安全是最容易被加入到你的Webservice应用中的,利用现有的SSL 和HTTPS协议,就可以很容易的获得连接过程中的安全。

然而这种安全实现方法有两个弱点。一是它只能保证数据传输的安全,而不是数据本身的安全,数据一旦到达某地,那么就可以被任何人所查看。而在Webservice中,一份数据可能到达多个地方,而这份数据却不该被所有的接受者所查看。二是它提供的是要么全有要么全无的保护,你不能选择哪部分数据要被保护,而这种可选择性也是在Webservice中所常要用到的。

第二层的保护是对于消息本身的保护。你可以使用已有的XML安全扩展标准,实现数字签名的功能,从而保证你的消息是来自特定方并没有被修改过。XML文件的加密技术从更大程度上加强了WebService的安全,它能够定制数据传输到后,能否被接受者所查看,进一步完善了传输后的安全,业界也在不断的制定Webservice的安全标准,比如SAML 和 WS-Security。

一是利用WS-Security将签名和加密头加入SOAP消息;

另一个是利用数字证书和数字签名认证;

最后一层保护就是依靠底层架构的安全,这更多的来自于操作系统和某些中间件的保护。比如在J2EE中,主持WebService的应用服务器。目前很多的J2EE应用服务器都支持Java Authentication and Authorization Service (JAAS),这是最近被加入到J2SE 1.4当中的。利用主持Webservice的服务器,实现一些安全机制这是很自然的做法。另一种利用底层架构的安全方法就是,做一个独立的负责安全的服务器,Webservice的使用者和创建者都需要与之取得安全信任。

Web-Security概述

WS-Security(Web服务安全)是一种提供在Web Service上应用安全的方法的网络传输协议,协议包含了关于如何在Web Service消息上保证完整性和机密性的规约。WS-Security描述了如何将签名和加密头加入SOAP消息。除此以外,还描述了如何在消息中加入安全令牌,包括二进制安全令牌,如X.509认证证书和Kerberos门票(ticket)。WS-Security将安全特性放入SOAP消息头中在应用层处理,这样协议保证了端到端的安全。

CXF中使用Web-Security

在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=[**], 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>

--------------------------------------