SOAP 及其安全控制--转载
原文地址:http://my.oschina.net/huangyong/blog/287791
目錄[-]
- 1. 基于用戶令牌的身份認(rèn)證
- 2. 基于數(shù)字簽名的身份認(rèn)證
- 3. SOAP 消息的加密與解密
- 4. 總結(jié)
通過(guò)上一篇文章,相信您已經(jīng)學(xué)會(huì)了如何使用 CXF 開發(fā)基于 SOAP 的 WS 了。或許您目前對(duì)于底層原理性的東西還不太理解,心中難免會(huì)有些疑問(wèn):
什么是 WSDL?
什么是 SOAP?
如何能讓 SOAP 更加安全?
我將努力通過(guò)本文,針對(duì)以上問(wèn)題,讓您得到一個(gè)滿意的答案。
還等什么呢?就從 WSDL 開始吧!
WSDL 的全稱是 Web Services Description Language(Web 服務(wù)描述語(yǔ)言),用于描述 WS 的具體內(nèi)容。
當(dāng)您成功發(fā)布一個(gè) WS 后,就能在瀏覽器中通過(guò)一個(gè)地址查看基于 WSDL 文檔,它是一個(gè)基于 XML 的文檔。一個(gè)典型的 WSDL 地址如下:
http://localhost:8080/ws/soap/hello?wsdl
注意:WSDL 地址必須帶有一個(gè)?wsdl?參數(shù)。
在瀏覽器中,您會(huì)看到一個(gè)標(biāo)準(zhǔn)的 XML 文檔:
其中,definitions?是 WSDL 的根節(jié)點(diǎn),它包括兩個(gè)重要的屬性:
提示:可以在?javax.jws.WebService?注解中配置以上兩個(gè)屬性值,但這個(gè)配置一定要在 WS 實(shí)現(xiàn)類上進(jìn)行,WS 接口類只需標(biāo)注一個(gè) WebService 注解即可。
在 definitions 這個(gè)根節(jié)點(diǎn)下,有五種類型的子節(jié)點(diǎn),它們分別是:
其中包括了兩個(gè)重要信息:
提示:可在?javax.jws.WebService?注解中配置 portName 與 endpointInterface,同樣必須在 WS 實(shí)現(xiàn)類上配置。
如果說(shuō) WSDL 是用于描述 WS 是什么,那么 SOAP 就用來(lái)表示 WS 里有什么。
其實(shí) SOAP 就是一個(gè)信封(Envelope),在這個(gè)信封里包括兩個(gè)部分,一是頭(Header),二是體(Body)。用于傳輸?shù)臄?shù)據(jù)都放在 Body 中了,一些特殊的屬性需要放在 Header 中(下面會(huì)看到)。
一般情況下,將需要傳輸?shù)臄?shù)據(jù)放入 Body 中,而 Header 是沒(méi)有任何內(nèi)容的,看起來(lái)整個(gè) SOAP 消息是這樣的:
可見,HTTP 請(qǐng)求的 Request Header 與 Request Body,這正好與 SOAP 消息的結(jié)構(gòu)有著異曲同工之妙!
看到這里,您或許會(huì)有很多疑問(wèn):
沒(méi)錯(cuò)!這就是我們今天要展開討論的話題 —— 基于 SOAP 的安全控制。
在 WS 領(lǐng)域有一個(gè)很強(qiáng)悍的解決方案,名為?WS-Security,它僅僅是一個(gè)規(guī)范,在 Java 業(yè)界里有一個(gè)很權(quán)威的實(shí)現(xiàn),名為?WSS4J。
下面我將一步步讓您學(xué)會(huì),如何使用?Spring?+?CXF?+?WSS4J?實(shí)現(xiàn)一個(gè)安全可靠的 WS 調(diào)用框架。
其實(shí)您需要做也就是兩件事情:
怎樣對(duì) WS 進(jìn)行身份認(rèn)證呢?可使用如下解決方案:
1. 基于用戶令牌的身份認(rèn)證
第一步:添加 CXF 提供的 WS-Security 的 Maven 依賴
<dependency><groupId>org.apache.cxf</groupId><artifactId>cxf-rt-ws-security</artifactId><version>${cxf.version}</version> </dependency>其實(shí)底層實(shí)現(xiàn)還是 WSS4J,CXF 只是對(duì)其做了一個(gè)封裝而已。
第二步:完成服務(wù)端 CXF 相關(guān)配置
<?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><!-- 用戶認(rèn)證(明文密碼) --><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>首先定義了一個(gè)基于 WSS4J 的攔截器(WSS4JInInterceptor),然后通過(guò) 將其配置到 helloService 上,最后使用了 CXF 提供的 Bus 特性,只需要在 Bus 上配置一個(gè) logging feature,就可以監(jiān)控每次 WS 請(qǐng)求與響應(yīng)的日志了。
注意:這個(gè) WSS4JInInterceptor 是一個(gè) InInterceptor,表示對(duì)輸入的消息進(jìn)行攔截,同樣還有 OutInterceptor,表示對(duì)輸出的消息進(jìn)行攔截。由于以上是服務(wù)器端的配置,因此我們只需要配置 InInterceptor 即可,對(duì)于客戶端而言,我們可以配置 OutInterceptor(下面會(huì)看到)。
有必要對(duì)以上配置中,關(guān)于 WSS4JInInterceptor 的構(gòu)造器參數(shù)做一個(gè)說(shuō)明。
- action = UsernameToken:表示使用基于“用戶名令牌”的方式進(jìn)行身份認(rèn)證
- passwordType = PasswordText:表示密碼以明文方式出現(xiàn)
- passwordCallbackRef = serverPasswordCallback:需要提供一個(gè)用于密碼驗(yàn)證的回調(diào)處理器(CallbackHandler)
以下便是 ServerPasswordCallback 的具體實(shí)現(xiàn):
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);}} }可見,它實(shí)現(xiàn)了?javax.security.auth.callback.CallbackHandler?接口,這是 JDK 提供的用于安全認(rèn)證的回調(diào)處理器接口。在代碼中提供了兩個(gè)用戶,分別是 client 與 server,用戶名與密碼存放在 userMap 中。這里需要將 JDK 提供的?javax.security.auth.callback.Callback?轉(zhuǎn)型為 WSS4J 提供的?org.apache.wss4j.common.ext.WSPasswordCallback,在 handle 方法中實(shí)現(xiàn)對(duì)客戶端密碼的驗(yàn)證,最終需要將密碼放入 callback 對(duì)象中。
第三步:完成客戶端 CXF 相關(guān)配置
<?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><!-- 用戶認(rèn)證(明文密碼) --><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,它是一個(gè) OutInterceptor,使客戶端對(duì)輸出的消息進(jìn)行攔截。
WSS4JOutInterceptor 的配置基本上與 WSS4JInInterceptor 大同小異,這里需要提供客戶端的用戶名(user = client),還需要提供一個(gè)客戶端密碼回調(diào)處理器(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 無(wú)非設(shè)置客戶端用戶的密碼,其它的什么也不用做了。客戶端密碼只能通過(guò)回調(diào)處理器的方式來(lái)提供,而不能在 Spring 中配置。
第四步:調(diào)用 WS 并觀察控制臺(tái)日志
部署應(yīng)用并啟動(dòng) Tomcat,再次調(diào)用 WS,此時(shí)會(huì)在 Tomcat 控制臺(tái)里的 Inbound Message 中看到如下 Payload:
可見,在 SOAP Header 中提供了 UsernameToken 的相關(guān)信息,但 Username 與 Password 都是明文的,SOAP Body 也是明文的,這顯然不是最好的解決方案。
如果您將 passwordType 由 PasswordText 改為 PasswordDigest(服務(wù)端與客戶端都需要做同樣的修改),那么就會(huì)看到一個(gè)加密過(guò)的密碼:
除了這種基于用戶名與密碼的身份認(rèn)證以外,還有一種更安全的身份認(rèn)證方式,名為“數(shù)字簽名”。
2. 基于數(shù)字簽名的身份認(rèn)證
數(shù)字簽名從字面上理解就是一種基于數(shù)字的簽名方式。也就是說(shuō),當(dāng)客戶端發(fā)送 SOAP 消息時(shí),需要對(duì)其進(jìn)行“簽名”,來(lái)證實(shí)自己的身份,當(dāng)服務(wù)端接收 SOAP 消息時(shí),需要對(duì)其簽名進(jìn)行驗(yàn)證(簡(jiǎn)稱“驗(yàn)簽”)。
在客戶端與服務(wù)端上都有各自的“密鑰庫(kù)”,這個(gè)密鑰庫(kù)里存放了“密鑰對(duì)”,而密鑰對(duì)實(shí)際上是由“公鑰”與“私鑰”組成的。當(dāng)客戶端發(fā)送 SOAP 消息時(shí),需要使用自己的私鑰進(jìn)行簽名,當(dāng)客戶端接收 SOAP 消息時(shí),需要使用客戶端提供的公鑰進(jìn)行驗(yàn)簽。
因?yàn)橛姓?qǐng)求就有相應(yīng),所以客戶端與服務(wù)端的消息調(diào)用實(shí)際上是雙向的,也就是說(shuō),客戶端與服務(wù)端的密鑰庫(kù)里所存放的信息是這樣的:
- 客戶端密鑰庫(kù):客戶端的私鑰(用于簽名)、服務(wù)端的公鑰(用于驗(yàn)簽)
- 服務(wù)端密鑰庫(kù):服務(wù)端的私鑰(用于簽名)、客戶端的公鑰(用于驗(yàn)簽)
記住一句話:使用自己的私鑰進(jìn)行簽名,使用對(duì)方的公鑰進(jìn)行驗(yàn)簽。
可見生成密鑰庫(kù)是我們要做的第一件事情。
第一步:生成密鑰庫(kù)
現(xiàn)在您需要?jiǎng)?chuàng)建一個(gè)名為 keystore.bat 的批處理文件,其內(nèi)容如下:
<!-- 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?命令行工具,關(guān)于該命令的使用方法,可點(diǎn)擊以下鏈接:
http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html
運(yùn)行該批處理程序,將生成兩個(gè)文件:server_store.jks 與 client_store.jks,隨后將 server_store.jks 放入服務(wù)端的 classpath 下,將 client_store.jks 放入客戶端的 classpath 下。如果您在本機(jī)運(yùn)行,那么本機(jī)既是客戶端又是服務(wù)端。
第二步:完成服務(wù)端 CXF 相關(guān)配置
... <bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"><constructor-arg><map><!-- 驗(yàn)簽(使用對(duì)方的公鑰) --><entry key="action" value="Signature"/><entry key="signaturePropFile" value="server.properties"/></map></constructor-arg> </bean> ...其中 action 為 Signature,server.properties 內(nèi)容如下:
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 相關(guān)配置
... <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 內(nèi)容如下:
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)與密碼回調(diào)處理器(passwordCallbackRef)。
第四步:調(diào)用 WS 并觀察控制臺(tái)日志
可見,數(shù)字簽名確實(shí)是一種更為安全的身份認(rèn)證方式,但無(wú)法對(duì) SOAP Body 中的數(shù)據(jù)進(jìn)行加密,仍然是“world”。
究竟怎樣才能加密并解密 SOAP 消息中的數(shù)據(jù)呢?
3. SOAP 消息的加密與解密
WSS4J 除了提供簽名與驗(yàn)簽(Signature)這個(gè)特性以外,還提供了加密與解密(Encrypt)功能,您只需要在服務(wù)端與客戶端的配置中稍作修改即可。
服務(wù)端:
... <bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"><constructor-arg><map><!-- 驗(yàn)簽 與 解密 --><entry key="action" value="Signature Encrypt"/><!-- 驗(yàn)簽(使用對(duì)方的公鑰) --><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"/><!-- 加密(使用對(duì)方的公鑰) --><entry key="encryptionPropFile" value="client.properties"/><entry key="encryptionUser" value="server"/></map></constructor-arg> </bean> ...可見,客戶端發(fā)送 SOAP 消息時(shí)進(jìn)行簽名(使用自己的私鑰)與加密(使用對(duì)方的公鑰),服務(wù)端接收 SOAP 消息時(shí)進(jìn)行驗(yàn)簽(使用對(duì)方的公鑰)與解密(使用自己的私鑰)。
現(xiàn)在您看到的 SOAP 消息應(yīng)該是這樣的:
可見,SOAP 請(qǐng)求不僅簽名了,而且還加密了,這樣的通訊更加安全可靠。
但是還存在一個(gè)問(wèn)題,雖然 SOAP 請(qǐng)求已經(jīng)很安全了,但 SOAP 響應(yīng)卻沒(méi)有做任何安全控制,看看下面的 SOAP 響應(yīng)吧:
如何才能對(duì) SOAP 響應(yīng)進(jìn)行簽名與加密呢?相信您一定有辦法做到,不妨親自動(dòng)手試一試吧!
4. 總結(jié)
本文的內(nèi)容有些多,確實(shí)需要稍微總結(jié)一下:
關(guān)于“SOAP 安全控制”也就這點(diǎn)事兒了,但關(guān)于“WS 那點(diǎn)事兒”還并沒(méi)有結(jié)束,因?yàn)?RESTful Web Services 在等著您。如何發(fā)布 REST 服務(wù)?如何對(duì) REST 服務(wù)進(jìn)行安全控制?我們下次再見!
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/4343027.html
總結(jié)
以上是生活随笔為你收集整理的SOAP 及其安全控制--转载的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: SQL 四种连接:内连接、左外连接、右外
- 下一篇: How to setup SLF4J a