天天看點

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>

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