SOAP 及其安全控制--转载
原文地址:http://my.oschina.net/huangyong/blog/287791
目錄[-]
- 1. 基于用戶令牌的身份認證
- 2. 基于數字簽名的身份認證
- 3. SOAP 消息的加密與解密
- 4. 總結
通過上一篇文章,相信您已經學會了如何使用 CXF 開發基于 SOAP 的 WS 了。或許您目前對于底層原理性的東西還不太理解,心中難免會有些疑問:
什么是 WSDL?
什么是 SOAP?
如何能讓 SOAP 更加安全?
我將努力通過本文,針對以上問題,讓您得到一個滿意的答案。
還等什么呢?就從 WSDL 開始吧!
WSDL 的全稱是 Web Services Description Language(Web 服務描述語言),用于描述 WS 的具體內容。
當您成功發布一個 WS 后,就能在瀏覽器中通過一個地址查看基于 WSDL 文檔,它是一個基于 XML 的文檔。一個典型的 WSDL 地址如下:
http://localhost:8080/ws/soap/hello?wsdl
注意:WSDL 地址必須帶有一個?wsdl?參數。
在瀏覽器中,您會看到一個標準的 XML 文檔:
其中,definitions?是 WSDL 的根節點,它包括兩個重要的屬性:
提示:可以在?javax.jws.WebService?注解中配置以上兩個屬性值,但這個配置一定要在 WS 實現類上進行,WS 接口類只需標注一個 WebService 注解即可。
在 definitions 這個根節點下,有五種類型的子節點,它們分別是:
其中包括了兩個重要信息:
提示:可在?javax.jws.WebService?注解中配置 portName 與 endpointInterface,同樣必須在 WS 實現類上配置。
如果說 WSDL 是用于描述 WS 是什么,那么 SOAP 就用來表示 WS 里有什么。
其實 SOAP 就是一個信封(Envelope),在這個信封里包括兩個部分,一是頭(Header),二是體(Body)。用于傳輸的數據都放在 Body 中了,一些特殊的屬性需要放在 Header 中(下面會看到)。
一般情況下,將需要傳輸的數據放入 Body 中,而 Header 是沒有任何內容的,看起來整個 SOAP 消息是這樣的:
可見,HTTP 請求的 Request Header 與 Request Body,這正好與 SOAP 消息的結構有著異曲同工之妙!
看到這里,您或許會有很多疑問:
沒錯!這就是我們今天要展開討論的話題 —— 基于 SOAP 的安全控制。
在 WS 領域有一個很強悍的解決方案,名為?WS-Security,它僅僅是一個規范,在 Java 業界里有一個很權威的實現,名為?WSS4J。
下面我將一步步讓您學會,如何使用?Spring?+?CXF?+?WSS4J?實現一個安全可靠的 WS 調用框架。
其實您需要做也就是兩件事情:
怎樣對 WS 進行身份認證呢?可使用如下解決方案:
1. 基于用戶令牌的身份認證
第一步:添加 CXF 提供的 WS-Security 的 Maven 依賴
<dependency><groupId>org.apache.cxf</groupId><artifactId>cxf-rt-ws-security</artifactId><version>${cxf.version}</version> </dependency>其實底層實現還是 WSS4J,CXF 只是對其做了一個封裝而已。
第二步:完成服務端 CXF 相關配置
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://cxf.apache.org/corehttp://cxf.apache.org/schemas/core.xsdhttp://cxf.apache.org/jaxwshttp://cxf.apache.org/schemas/jaxws.xsd"><bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"><constructor-arg><map><!-- 用戶認證(明文密碼) --><entry key="action" value="UsernameToken"/><entry key="passwordType" value="PasswordText"/><entry key="passwordCallbackRef" value-ref="serverPasswordCallback"/></map></constructor-arg></bean><jaxws:endpoint id="helloService" implementor="#helloServiceImpl" address="/soap/hello"><jaxws:inInterceptors><ref bean="wss4jInInterceptor"/></jaxws:inInterceptors></jaxws:endpoint><cxf:bus><cxf:features><cxf:logging/></cxf:features></cxf:bus></beans>首先定義了一個基于 WSS4J 的攔截器(WSS4JInInterceptor),然后通過 將其配置到 helloService 上,最后使用了 CXF 提供的 Bus 特性,只需要在 Bus 上配置一個 logging feature,就可以監控每次 WS 請求與響應的日志了。
注意:這個 WSS4JInInterceptor 是一個 InInterceptor,表示對輸入的消息進行攔截,同樣還有 OutInterceptor,表示對輸出的消息進行攔截。由于以上是服務器端的配置,因此我們只需要配置 InInterceptor 即可,對于客戶端而言,我們可以配置 OutInterceptor(下面會看到)。
有必要對以上配置中,關于 WSS4JInInterceptor 的構造器參數做一個說明。
- action = UsernameToken:表示使用基于“用戶名令牌”的方式進行身份認證
- passwordType = PasswordText:表示密碼以明文方式出現
- passwordCallbackRef = serverPasswordCallback:需要提供一個用于密碼驗證的回調處理器(CallbackHandler)
以下便是 ServerPasswordCallback 的具體實現:
package demo.ws.soap_spring_cxf_wss4j;import java.io.IOException; import java.util.HashMap; import java.util.Map; 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; import org.springframework.stereotype.Component;@Component public class ServerPasswordCallback implements CallbackHandler {private static final Map<String, String> userMap = new HashMap<String, String>();static {userMap.put("client", "clientpass");userMap.put("server", "serverpass");}@Overridepublic void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];String clientUsername = callback.getIdentifier();String serverPassword = userMap.get(clientUsername);if (serverPassword != null) {callback.setPassword(serverPassword);}} }可見,它實現了?javax.security.auth.callback.CallbackHandler?接口,這是 JDK 提供的用于安全認證的回調處理器接口。在代碼中提供了兩個用戶,分別是 client 與 server,用戶名與密碼存放在 userMap 中。這里需要將 JDK 提供的?javax.security.auth.callback.Callback?轉型為 WSS4J 提供的?org.apache.wss4j.common.ext.WSPasswordCallback,在 handle 方法中實現對客戶端密碼的驗證,最終需要將密碼放入 callback 對象中。
第三步:完成客戶端 CXF 相關配置
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://cxf.apache.org/jaxwshttp://cxf.apache.org/schemas/jaxws.xsd"><context:component-scan base-package="demo.ws"/><bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"><constructor-arg><map><!-- 用戶認證(明文密碼) --><entry key="action" value="UsernameToken"/><entry key="user" value="client"/><entry key="passwordType" value="PasswordText"/><entry key="passwordCallbackRef" value-ref="clientPasswordCallback"/></map></constructor-arg></bean><jaxws:client id="helloService"serviceClass="demo.ws.soap_spring_cxf_wss4j.HelloService"address="http://localhost:8080/ws/soap/hello"><jaxws:outInterceptors><ref bean="wss4jOutInterceptor"/></jaxws:outInterceptors></jaxws:client></beans>注意:這里使用的是 WSS4JOutInterceptor,它是一個 OutInterceptor,使客戶端對輸出的消息進行攔截。
WSS4JOutInterceptor 的配置基本上與 WSS4JInInterceptor 大同小異,這里需要提供客戶端的用戶名(user = client),還需要提供一個客戶端密碼回調處理器(passwordCallbackRef = clientPasswordCallback),代碼如下:
package demo.ws.soap_spring_cxf_wss4j;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; import org.springframework.stereotype.Component;@Component public class ClientPasswordCallback implements CallbackHandler {@Overridepublic void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];callback.setPassword("clientpass");} }在 ClientPasswordCallback 無非設置客戶端用戶的密碼,其它的什么也不用做了。客戶端密碼只能通過回調處理器的方式來提供,而不能在 Spring 中配置。
第四步:調用 WS 并觀察控制臺日志
部署應用并啟動 Tomcat,再次調用 WS,此時會在 Tomcat 控制臺里的 Inbound Message 中看到如下 Payload:
可見,在 SOAP Header 中提供了 UsernameToken 的相關信息,但 Username 與 Password 都是明文的,SOAP Body 也是明文的,這顯然不是最好的解決方案。
如果您將 passwordType 由 PasswordText 改為 PasswordDigest(服務端與客戶端都需要做同樣的修改),那么就會看到一個加密過的密碼:
除了這種基于用戶名與密碼的身份認證以外,還有一種更安全的身份認證方式,名為“數字簽名”。
2. 基于數字簽名的身份認證
數字簽名從字面上理解就是一種基于數字的簽名方式。也就是說,當客戶端發送 SOAP 消息時,需要對其進行“簽名”,來證實自己的身份,當服務端接收 SOAP 消息時,需要對其簽名進行驗證(簡稱“驗簽”)。
在客戶端與服務端上都有各自的“密鑰庫”,這個密鑰庫里存放了“密鑰對”,而密鑰對實際上是由“公鑰”與“私鑰”組成的。當客戶端發送 SOAP 消息時,需要使用自己的私鑰進行簽名,當客戶端接收 SOAP 消息時,需要使用客戶端提供的公鑰進行驗簽。
因為有請求就有相應,所以客戶端與服務端的消息調用實際上是雙向的,也就是說,客戶端與服務端的密鑰庫里所存放的信息是這樣的:
- 客戶端密鑰庫:客戶端的私鑰(用于簽名)、服務端的公鑰(用于驗簽)
- 服務端密鑰庫:服務端的私鑰(用于簽名)、客戶端的公鑰(用于驗簽)
記住一句話:使用自己的私鑰進行簽名,使用對方的公鑰進行驗簽。
可見生成密鑰庫是我們要做的第一件事情。
第一步:生成密鑰庫
現在您需要創建一個名為 keystore.bat 的批處理文件,其內容如下:
<!-- lang: shell --> @echo offkeytool -genkeypair -alias server -keyalg RSA -dname "cn=server" -keypass serverpass -keystore server_store.jks -storepass storepass keytool -exportcert -alias server -file server_key.rsa -keystore server_store.jks -storepass storepass keytool -importcert -alias server -file server_key.rsa -keystore client_store.jks -storepass storepass -noprompt del server_key.rsakeytool -genkeypair -alias client -dname "cn=client" -keyalg RSA -keypass clientpass -keystore client_store.jks -storepass storepass keytool -exportcert -alias client -file client_key.rsa -keystore client_store.jks -storepass storepass keytool -importcert -alias client -file client_key.rsa -keystore server_store.jks -storepass storepass -noprompt del client_key.rsa在以上這些命令中,使用了 JDK 提供的?keytool?命令行工具,關于該命令的使用方法,可點擊以下鏈接:
http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html
運行該批處理程序,將生成兩個文件:server_store.jks 與 client_store.jks,隨后將 server_store.jks 放入服務端的 classpath 下,將 client_store.jks 放入客戶端的 classpath 下。如果您在本機運行,那么本機既是客戶端又是服務端。
第二步:完成服務端 CXF 相關配置
... <bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"><constructor-arg><map><!-- 驗簽(使用對方的公鑰) --><entry key="action" value="Signature"/><entry key="signaturePropFile" value="server.properties"/></map></constructor-arg> </bean> ...其中 action 為 Signature,server.properties 內容如下:
org.apache.ws.security.crypto.provider=org.apache.wss4j.common.crypto.Merlin org.apache.ws.security.crypto.merlin.file=server_store.jks org.apache.ws.security.crypto.merlin.keystore.type=jks org.apache.ws.security.crypto.merlin.keystore.password=storepass第三步:完成客戶端 CXF 相關配置
... <bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"><constructor-arg><map><!-- 簽名(使用自己的私鑰) --><entry key="action" value="Signature"/><entry key="signaturePropFile" value="client.properties"/><entry key="signatureUser" value="client"/><entry key="passwordCallbackRef" value-ref="clientPasswordCallback"/></map></constructor-arg> </bean> ...其中 action 為 Signature,client.properties 內容如下:
org.apache.ws.security.crypto.provider=org.apache.wss4j.common.crypto.Merlin org.apache.ws.security.crypto.merlin.file=client_store.jks org.apache.ws.security.crypto.merlin.keystore.type=jks org.apache.ws.security.crypto.merlin.keystore.password=storepass此外,客戶端同樣需要提供簽名用戶(signatureUser)與密碼回調處理器(passwordCallbackRef)。
第四步:調用 WS 并觀察控制臺日志
可見,數字簽名確實是一種更為安全的身份認證方式,但無法對 SOAP Body 中的數據進行加密,仍然是“world”。
究竟怎樣才能加密并解密 SOAP 消息中的數據呢?
3. SOAP 消息的加密與解密
WSS4J 除了提供簽名與驗簽(Signature)這個特性以外,還提供了加密與解密(Encrypt)功能,您只需要在服務端與客戶端的配置中稍作修改即可。
服務端:
... <bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"><constructor-arg><map><!-- 驗簽 與 解密 --><entry key="action" value="Signature Encrypt"/><!-- 驗簽(使用對方的公鑰) --><entry key="signaturePropFile" value="server.properties"/><!-- 解密(使用自己的私鑰) --><entry key="decryptionPropFile" value="server.properties"/><entry key="passwordCallbackRef" value-ref="serverPasswordCallback"/></map></constructor-arg> </bean> ...客戶端:
... <bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"><constructor-arg><map><!-- 簽名 與 加密 --><entry key="action" value="Signature Encrypt"/><!-- 簽名(使用自己的私鑰) --><entry key="signaturePropFile" value="client.properties"/><entry key="signatureUser" value="client"/><entry key="passwordCallbackRef" value-ref="clientPasswordCallback"/><!-- 加密(使用對方的公鑰) --><entry key="encryptionPropFile" value="client.properties"/><entry key="encryptionUser" value="server"/></map></constructor-arg> </bean> ...可見,客戶端發送 SOAP 消息時進行簽名(使用自己的私鑰)與加密(使用對方的公鑰),服務端接收 SOAP 消息時進行驗簽(使用對方的公鑰)與解密(使用自己的私鑰)。
現在您看到的 SOAP 消息應該是這樣的:
可見,SOAP 請求不僅簽名了,而且還加密了,這樣的通訊更加安全可靠。
但是還存在一個問題,雖然 SOAP 請求已經很安全了,但 SOAP 響應卻沒有做任何安全控制,看看下面的 SOAP 響應吧:
如何才能對 SOAP 響應進行簽名與加密呢?相信您一定有辦法做到,不妨親自動手試一試吧!
4. 總結
本文的內容有些多,確實需要稍微總結一下:
關于“SOAP 安全控制”也就這點事兒了,但關于“WS 那點事兒”還并沒有結束,因為 RESTful Web Services 在等著您。如何發布 REST 服務?如何對 REST 服務進行安全控制?我們下次再見!
轉載于:https://www.cnblogs.com/davidwang456/p/4343027.html
總結
以上是生活随笔為你收集整理的SOAP 及其安全控制--转载的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL 四种连接:内连接、左外连接、右外
- 下一篇: How to setup SLF4J a