正如 “Metro 簡介” 所述,JAXB 2.x 數據綁定的參考實現和 JAX-WS 2.x 網絡服務規范是 Metro 網絡服務框架的核心。但是 JAXB 與 JAX-WS 本身只提供基本的網絡服務支持,JAX-WS 並不包括 WS-* 技術,不能升級 SOAP 以便在企業級應用環境中工作,所以需要一些其他的軟件組件來支持 WS-* 技術。
在 Metro 中,添加的主要組件是 Web 服務互操作性技術(Web Services Interoperability Technologies,WSIT)。WSIT 是原來稱為 Project Tango 的當前版本,Project Tango 是 Sun 公司為 實現某些 WS-* 特性(包括安全可靠的消息傳遞)與 Microsoft .NET 平台之間的互操作性而付出的努力 。WSIT 向 Metro 提供 WS-SecurityPolicy、WS-Trust、WS-SecureConversation、WS- ReliableMessaging 等服務規范支持。而 WS-Security 的實際運行時處理由另外一個添加的組件 —— XML and WebServices Security Project (XWSS) 來實現。
本文展示如何通過 Metro 來使用和配置 WS-Security,將其作為一個獨立的 Web 應用程序在 Glassfish 服務器外部使用。參見 下載 部分獲取本文中的全部示例的完整源代碼,該代碼實現了此前在 本系列中使用的簡單圖書館管理服務。
WSIT 基礎
WSIT 負責配置 Metro 運行時以匹配一個服務的 WS-Policy 規范,包括諸如 WS-SecurityPolicy 之 類的 WS-Policy 擴展。除標准的 WS-Policy 擴展外,Metro 還使用策略文檔中的自定義擴展來配置實現 安全處理所需的用戶信息(如密匙存儲位置和密碼)。
WSIT 從 Web Services Description Language (WSDL) 服務描述中獲取策略信息。在客戶端,這可能 難以理解一些,因為用於 WSIT 配置的 WSDL 與用於定義 JAX-WS 服務的 WSDL 是分開的。如 “Metro 簡介” 所述,用於配置 JAX-WS 客戶端的 WSDL 可以直接從服務獲取,也可以從生成 JAX-WS 代碼之時 指定的位置獲取。WSIT 使用的 WSDL 有一個固定的文件名(雖然這個文件能夠使用一個 <wsdl:import> 標記來引用一個帶有完整 WSDL 的獨立文件),並且總是通過類路徑訪問。
在服務器端,WSIT 要求在 WEB-INF/sun-jaxws.xml 配置文件(在 “Metro 簡介” 中介紹過)中指 定的位置提供 WSDL。所提供的 WSDL 必須包含用於為 WSIT 配置用戶信息的自定義擴展,但是這些自定 義擴展在為響應對服務端點的 HTT PGET 請求而提供的 WSDL 版本中被刪除掉了。
這些用於配置 WSIT 用戶信息的自定義擴展在服務器端和客戶端看起來一樣,但是在用於擴展元素的 XML 名稱空間中不同。在客戶端,名稱空間是 http://schemas.sun.com/2006/03/wss/client;在服務器 端,名稱空間是 http://schemas.sun.com/2006/03/wss/server。
Metro 中的 UsernameToken
“Axis2 WS-Security 基礎” 使用了一個簡單的 UsernameToken 例子來介紹 Axis2/Rampart 環境下 的 WS-Security。UsernameToken 提供了一種通過 WS-Security 來表示 “用戶名/密碼” 對的標准方法 。密碼信息可以以純文本方式(通常,這種方式只有在同時配置了傳輸層安全協議(Transport Layer Security,TLS)或者使用 WS-Security 加密時才會在生產中使用 — 但是這種方式確實便於測試)或者 以散列值方式發送。
要在 Metro 上實現一個簡單的純文本格式的 UsernameToken 示例,您需要在 WSDL 服務定義中恰當 定義 WS-Policy/WS-SecurityPolicy。清單 1 展示了 “Metro 簡介” 中使用過的一個基礎 WSDL 服務 定義的修訂版,包含了在從客戶端到服務器的請求上要求 UsernameToken 的策略信息。與策略本身一樣 ,<wsdl:binding> 中的策略引用以粗體顯示。
清單 1. 純文本 UsernameToken WSDL
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://ws.sosnoski.com/library/wsdl"
xmlns:wns="http://ws.sosnoski.com/library/wsdl"
xmlns:tns="http://ws.sosnoski.com/library/types"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
<wsdl:types>
...
</wsdl:types>
<wsdl:message name="getBookRequest">
<wsdl:part element="wns:getBook" name="parameters"/>
</wsdl:message>
...
<wsdl:portType name="Library">
<wsdl:operation name="getBook">
<wsdl:input message="wns:getBookRequest" name="getBookRequest"/>
<wsdl:output message="wns:getBookResponse" name="getBookResponse"/>
</wsdl:operation>
...
</wsdl:portType>
<wsdl:binding name="LibrarySoapBinding" type="wns:Library">
<wsp:PolicyReference xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
URI="#UsernameToken"/>
<wsdlsoap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getBook">
<wsdlsoap:operation soapAction="urn:getBook"/>
<wsdl:input name="getBookRequest">
<wsdlsoap:body use="literal"/>
</wsdl:input>
<wsdl:output name="getBookResponse">
<wsdlsoap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
...
</wsdl:binding>
<wsdl:service name="MetroLibrary">
<wsdl:port binding="wns:LibrarySoapBinding" name="library">
<wsdlsoap:address location="http://localhost:8080/metro-library-username"/>
</wsdl:port>
</wsdl:service>
<!-- Policy for Username Token with plaintext password, sent from client to
server only -->
<wsp:Policy wsu:Id="UsernameToken" xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:SupportingTokens
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:UsernameToken sp:IncludeToken=".../IncludeToken/AlwaysToRecipient"/>
</wsp:Policy>
</sp:SupportingTokens>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
</wsdl:definitions>
清單 1 WSDL 告訴我們,要訪問服務需要進行哪些安全處理。您需要同時在服務器端和客戶端向策略 信息中添加 WSDL 自定義擴展,通過用戶配置細節表明如何實現安全處理。這些自定義擴展被加入到了 WSDL 中的 <wsp:Policy> 組件。下一步,我將向您展示每一端的擴展示例。
客戶端應用
在客戶端,使用一個名為 wsit-client.xml (這個文件名是固定的)的文件來進行 WSIT 配置。這個 文件必須位於根目錄的路徑下(不在任何包中),或者在類路徑的一個目錄的 META-INF 子目錄中。而 wsit-client.xml 必須是能直接提供全部 WSDL 服務或通過 <wsdl:import> 引用某個獨立的 WSDL 服務定義的 WSDL 文檔。無論哪一種方式,WSDL 都必須包含 WS-Policy/WS-SecurityPolicy 的全部要求 和 WSIT 配置擴展。
清單 2 展示了 清單 1 WSDL 中的策略部分,通過添加一個 WSIT 自定義擴展來配置客戶端 UsernameToken 支持。在這裡,那個自定義擴展是 <wssc:CallbackHandlerConfiguration> 元素 及其子元素,以粗體顯示。兩個 <wssc:CallbackHandler> 子元素定義回調類,第一個定義用戶名 (name="usernameHandler"),第二個定義密碼(name="passwordHandler")。指定的類必須實現 javax.security.auth.callback.CallbackHandler 接口。
清單 2. 帶有 WSIT 客戶端擴展的 UsernameToken 策略
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="UsernameToken">
<wsp:ExactlyOne>
<wsp:All>
<sp:SupportingTokens
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:UsernameToken sp:IncludeToken=".../IncludeToken/AlwaysToRecipient"/>
</wsp:Policy>
</sp:SupportingTokens>
<wssc:CallbackHandlerConfiguration wspp:visibility="private"
xmlns:wssc="http://schemas.sun.com/2006/03/wss/client"
xmlns:wspp="http://java.sun.com/xml/ns/wsit/policy">
<wssc:CallbackHandler name="usernameHandler"
classname="com.sosnoski.ws.library.metro.UserPassCallbackHandler"/>
<wssc:CallbackHandler name="passwordHandler"
classname="com.sosnoski.ws.library.metro.UserPassCallbackHandler"/>
</wssc:CallbackHandlerConfiguration>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
在 清單 2 中,所有的回調必須使用相同的類。清單 3 是回調類的代碼,作用是檢查每個回調請求的 類型並恰當地為其賦值:
清單3. 客戶端回調代碼
public class UserPassCallbackHandler implements CallbackHandler
{
public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback)callbacks[i]).setName("libuser");
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback)callbacks[i]).setPassword("books".toCharArray());
} else {
throw new UnsupportedCallbackException(callbacks[i],
"Unsupported callback type");
}
}
}
}
您並不是必須 使用回調來設置用戶名或密碼。如果用戶名和密碼被賦予的是固定值,您可以直接在 <wssc:CallbackHandler> 元素中設置它們,方法是使用 default="yyy"(在這裡,屬性值即實際 的用戶名和密碼)來替換 classname="xxx" 屬性。
服務器端應用
在服務器端,WSIT 配置信息需要包含在 WSDL 服務定義中。如 “Metro 簡介” 所述,服務 WSDL 的 位置可以在服務 WAR 文件中的 WEB-INF/sun-jaxws.xml 裡指定為一個參數。如果不使用 WSIT 特性,則 WSDL 是可選的;在這種情況下,WSDL 將在運行時自動生成。如果使用 WSIT 功能,則 WSDL 是必須的, 並且必須包含為服務所用的特性配置 WSIT 所需的任意自定義擴展元素。清單 4 展示了 清單 1 WSDL 服 務的策略部分,這次添加了一個 WSIT 自定義擴展元素來配置 UsernameToken 支持(以粗體顯示):
清單 4. 帶有 WSIT 服務器端擴展的 UsernameToken 策略
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="UsernameToken">
<wsp:ExactlyOne>
<wsp:All>
<sp:SupportingTokens
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:UsernameToken sp:IncludeToken=".../IncludeToken/AlwaysToRecipient"/>
</wsp:Policy>
</sp:SupportingTokens>
<wsss:ValidatorConfiguration wspp:visibility="private"
xmlns:wsss="http://schemas.sun.com/2006/03/wss/server"
xmlns:wspp="http://java.sun.com/xml/ns/wsit/policy">
<wsss:Validator name="usernameValidator"
classname="com.sosnoski.ws.library.metro.PasswordValidator"/>
</wsss:ValidatorConfiguration>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
清單 4 中的服務器端 WSIT 擴展以 <wsss:ValidatorConfiguration> 元素和 <wsss:Validator> 子元素形式表示,指定將作為驗證器回調使用的類。清單 5 給出了這個類的代 碼,這個類必須實現 com.sun.xml.wss.impl.callback.PasswordValidationCallback.PasswordValidator 接口。在這裡,它 只是針對固定值檢查提供的用戶名和密碼,但它可以輕松使用一個數據庫查詢或其他機制替代。
清單5. 服務器回調代碼
public class PasswordValidator implements PasswordValidationCallback.PasswordValidator
{
public boolean validate(Request request) throws PasswordValidationException {
PasswordValidationCallback.PlainTextPasswordRequest ptreq
= (PasswordValidationCallback.PlainTextPasswordRequest)request;
return "libuser".equals(ptreq.getUsername()) &&
"books".equals(ptreq.getPassword());
}
}
Metro 策略工具
Metro/WSIT 要求您向 WSDL 文件添加配置信息,就像使用 Axis2/Rampart 一樣。這個系列中此前介 紹 Axis2/Rampart 的文章在構建過程中使用了一種特殊的策略工具來根據需要生成經過修改的 WSDL 文 件。本文的示例代碼 下載 部分包含一種相似的工具,它根據 Metro/WSIT 的需要而設計。
這個工具就是 com.sosnoski.ws.MergeTool 應用程序,在示例代碼的 mergetool 目錄中。MergeTool 用於將數據合並到目標 XML 文件中,匹配嵌套的 XML 元素,找到需要合並的數據並在目標文檔中確定數 據的合並點。示例程序的 build.xml 使用 MergeTool 將客戶端或服務器的 WSIT 配置信息添加到服務的 WSDL 中。如果願意,您也可以在自己的應用程序中使用 MergeTool — mergetool/readme.txt 文件包含 一些基礎的使用說明,您也可以在提供的構建中看到 MergeTool 的使用方法。
如果沒有 <wsss:ValidatorConfiguration>,Metro 將使用您的 Web 應用程序容器(提供 servlet 的 Web 服務器)所提供的授權機制。
構建並運行示例代碼
在調試示例代碼之前,您需要在您的操作系統中下載並且安裝一個最新版本的 Metro。您還需要對解 壓後的示例代碼 下載 根目錄下的 build.properties 文件進行一些編輯,把 metro-home 屬性值改成您 的 Metro 安裝路徑。如果您還打算測試一個不同操作系統或端口上的服務器,那麼您需要修改 host- name 和 host-port。
要使用已提供的 Ant build.xml 構建示例應用程序,需要對下載代碼的根目錄和 ant 類型打開控制 台。這將首先調用 JAX-WS wsimport 工具(包含在 Metro 發行版中),然後構建客戶端和服務器,最後 把服務器端代碼打包為一個 WAR(這個過程將生成包含客戶端與服務器 WSIT 配置信息的服務 WSDL 的獨 立版本)。注意,包含在 Metro 1.5 中的 wsimport 版本會彈出一條警告信息(因為該工具在處理 WSDL 中嵌套的模式時有一個怪癖):src-resolve: Cannot resolve the name 'tns:BookInformation' to a (n) 'type definition' component。
這時您可以將 metro-library.war 文件部署到您的測試服務器中,然後在控制台上輸入 ant run 來 運行示例客戶端。示例客戶端將向服務器發送一系列請求,並輸出每個請求的簡單結果。
在 Metro 中簽名與加密
UsernameToken 的簡潔性使其成為一個不錯的起點,但這並不是 WS-Security 的典型應用。在大多數 情況下,您可能會使用簽名,或者加密,或者兩者都使用。清單 6 展示了一個修改過的、同時使用簽名 與加密的 WSDL 示例(基於 “Axis2 WS-Security 簽名和加密” 中的示例 — 請參考那篇文章了解關於 WS-Security 簽名與加密技術的詳細信息)。WSDL 的策略部分以粗體顯示。
清單 6. 簽名/加密 WSDL
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://ws.sosnoski.com/library/wsdl"
xmlns:wns="http://ws.sosnoski.com/library/wsdl"
xmlns:tns="http://ws.sosnoski.com/library/types"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
<wsdl:types>
...
</wsdl:types>
<wsdl:message name="getBookRequest">
<wsdl:part element="wns:getBook" name="parameters"/>
</wsdl:message>
...
<wsdl:portType name="Library">
<wsdl:operation name="getBook">
<wsdl:input message="wns:getBookRequest" name="getBookRequest"/>
<wsdl:output message="wns:getBookResponse" name="getBookResponse"/>
</wsdl:operation>
...
</wsdl:portType>
<wsdl:binding name="LibrarySoapBinding" type="wns:Library">
<wsp:PolicyReference xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
URI="#SignEncr"/>
<wsdlsoap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getBook">
<wsdlsoap:operation soapAction="urn:getBook"/>
<wsdl:input name="getBookRequest">
<wsdlsoap:body use="literal"/>
</wsdl:input>
<wsdl:output name="getBookResponse">
<wsdlsoap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
...
</wsdl:binding>
<wsdl:service name="MetroLibrary">
<wsdl:port binding="wns:LibrarySoapBinding" name="library">
<wsdlsoap:address location="http://localhost:8080/metro-library-username"/>
</wsdl:port>
</wsdl:service>
<!-- Policy for first signing and then encrypting all messages, with the certificate
included in the message from client to server but only a thumbprint on messages from
the server to the client. -->
<wsp:Policy wsu:Id="SignEncr" xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:AsymmetricBinding
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:InitiatorToken>
<wsp:Policy>
<sp:X509Token sp:IncludeToken=".../IncludeToken/AlwaysToRecipient">
<!-- Added this policy component so Metro would work with the same
certificates (and key stores) used in the Axis2/Rampart example. -->
<wsp:Policy>
<sp:RequireThumbprintReference/>
</wsp:Policy>
</sp:X509Token>
</wsp:Policy>
</sp:InitiatorToken>
<sp:RecipientToken>
<wsp:Policy>
<sp:X509Token sp:IncludeToken=".../IncludeToken/Never">
<wsp:Policy>
<sp:RequireThumbprintReference/>
</wsp:Policy>
</sp:X509Token>
</wsp:Policy>
</sp:RecipientToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:TripleDesRsa15/>
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Strict/>
</wsp:Policy>
</sp:Layout>
<sp:IncludeTimestamp/>
<sp:OnlySignEntireHeadersAndBody/>
</wsp:Policy>
</sp:AsymmetricBinding>
<sp:SignedParts
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<sp:Body/>
</sp:SignedParts>
<sp:EncryptedParts
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<sp:Body/>
</sp:EncryptedParts>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
</wsdl:definitions>
清單 6 WSDL 與先前的 Axis2/Rampart 示例中使用過的 WSDL 的唯一重要區別是:當 X509 證書沒有 被包含到一條信息中時,需要向 <sp:InitiatorToken> 組件添加一個要求使用拇指指紋引用的策 略,其原因是 Metro 和 Axis2 中引用的默認處理方式不同。
當客戶端(在 WS-SecurityPolicy 術語中稱為發起者)發送一條信息時,客戶端的 X.509 證書也作 為信息的一部分發送(因為<sp:InitiatorToken/wsp:Policy/sp:X509Token> 元素上的 sp:IncludeToken=".../IncludeToken/AlwaysToRecipient" 屬性),然後服務器使用該證書進行簽名驗 證。當服務器對客戶端進行應答時,進行加密處理時需要引用那個證書。如果沒有指定其他方法, Axis2/Rampart 默認使用一個拇指指紋引用進行證書識別。Metro/WSIT 默認使用另一種方法,稱為主體 密匙標識符(subject key identifier,SKI)。Axis2/Rampart 示例中使用的證書的形式並不支持 SKI ,所以它們默認不能用於 Metro/WSIT。向策略中添加 <sp:RequireThumbprintReference/> 元素 告知 Metro/WSIT 使用拇指指紋引用來代替證書。
這種策略改變使先前的 Axis2/Rampart 示例使用的證書和密匙存儲可以在現在的示例中使用。這還使 Axis2/Rampart 客戶端示例可以與 Metro/WSIT 服務器一起使用,反之也然,從而作為一種檢查互操作性 的便捷方式。如果您進行這種嘗試(方法是修改每次傳送到測試客戶端的目標路徑),就會發現大部分消 息可以毫不費力地交換 — 但是在實際操作中有一個問題,這將在下面的 互操作性問題 小節進行討論。
與 UsernameToken 示例一樣,WSIT 需要客戶端與服務器上存在針對策略信息的自定義擴展,以便提 供詳細的附加配置信息。
客戶端應用
清單 7 展示了向 WSDL 策略添加的自定義擴展,用於針對本示例配置客戶端處理方式。這些自定義擴 展(以粗體顯示)配置密匙存儲(包含客戶端的私有密匙以及對應的證書)和簽名與加密所需的可信存儲 (包含服務器證書)。
清單 7. 使用 WSIT 客戶端擴展簽署並加密策略
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="SignEncr">
<wsp:ExactlyOne>
<wsp:All>
<sp:AsymmetricBinding xmlns:sp=
"http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
...
</wsp:Policy>
</sp:AsymmetricBinding>
<sp:SignedParts
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<sp:Body/>
</sp:SignedParts>
<sp:EncryptedParts
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<sp:Body/>
</sp:EncryptedParts>
<wssc:KeyStore alias="clientkey" keypass="clientpass"
location="client.keystore" storepass="nosecret"
xmlns:wspp="http://java.sun.com/xml/ns/wsit/policy" wspp:visibility="private"
xmlns:wssc="http://schemas.sun.com/2006/03/wss/client"/>
<wssc:TrustStore location="client.keystore" peeralias="serverkey"
storepass="nosecret" xmlns:wspp="http://java.sun.com/xml/ns/wsit/policy"
wspp:visibility="private"
xmlns:wssc="http://schemas.sun.com/2006/03/wss/client"/>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
清單 7 WSIT 自定義擴展提供了訪問密匙存儲和可信存儲(在本例中是同一個文件)所需的全部參數 ,包括訪問客戶端的私有密匙所需的密碼(<wssc:KeyStore> 元素上的 keypass="clientpass" 屬 性)。也可以使用回調獲取密碼信息,這將在下節介紹。
已命名的密匙存儲和可信存儲必須放在類路徑目錄下的 META-INF 子目錄中。也可以對這些文件使用 絕對路徑 — 而不是僅僅使用文件名 — 這允許您在文件系統的任意固定地址定位它們。(回想一下,對 於客戶端,包含 WSIT 自定義擴展的 WSDL 必須使用固定名稱 wsit-client.xml,而且必須在類路徑的根 目錄下或者類路徑根目錄下的 META-INF子目錄中)。
服務器端應用
添加到 WSDL 的服務器端 WSIT 自定義擴展在清單 8 中展示(同樣以粗體顯示)。在本例中, <wsss:KeyStore> 的 keypass 屬性給出的是一個類名而不是具體的密碼值(像 清單 7 客戶端示 例一樣)。如果您使用這種方法,被引用的類必須實現 javax.security.auth.callback.CallbackHandler 接口;當該類需要訪問秘密密匙的密碼時,它將被 WSIT 代碼調用。對於 storepass 值,您也可以使用相同的技術來指定一個類而不是密碼。
清單 8. 使用 WSIT 服務器端擴展簽署並加密策略
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="SignEncr">
<wsp:ExactlyOne>
<wsp:All>
<sp:AsymmetricBinding xmlns:sp=
"http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
...
</wsp:Policy>
</sp:AsymmetricBinding>
<sp:SignedParts
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<sp:Body/>
</sp:SignedParts>
<sp:EncryptedParts
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<sp:Body/>
</sp:EncryptedParts>
<wsss:KeyStore alias="serverkey"
keypass="com.sosnoski.ws.library.metro.KeystoreAccess"
location="server.keystore" storepass="nosecret"
xmlns:wspp="http://java.sun.com/xml/ns/wsit/policy" wspp:visibility="private"
xmlns:wsss="http://schemas.sun.com/2006/03/wss/server"/>
<wsss:TrustStore location="server.keystore" storepass="nosecret"
xmlns:wspp="http://java.sun.com/xml/ns/wsit/policy" wspp:visibility="private"
xmlns:wsss="http://schemas.sun.com/2006/03/wss/server"/>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
清單 9 展示了這個示例使用的 CallbackHandler 接口的實現:
清單 9. 服務器密匙存儲密碼回調代碼
public class KeystoreAccess implements CallbackHandler
{
public void handle(
Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
Callback callback = callbacks[i];
if (callback instanceof PasswordCallback) {
((PasswordCallback)callback).setPassword("serverpass".toCharArray());
} else {
throw new UnsupportedCallbackException(callback, "unknown callback");
}
}
}
}
構建並運行示例代碼
簽名與加密示例使用的 構建步驟 與 UsernameToken 示例相同,但是您必須修改 build.properties 文件以使用 variant-name=signencr(而不是 UsernameToken 示例使用的 username)。
互操作性問題
如果您使用 Axis2/Rampart 客戶端與 Metro/WSIT 服務器(或者相反),當客戶端嘗試添加一本國際 標准書籍編號(International Standard Book Number,ISBN)相同的書時,您可能會遇到問題。在這種 情況下,服務器將返回一個 Fault,而不是通常的 SOAP 響應消息。Axis2/Rampart 1.5.x 發布版正確地 執行了 WSDL 在這裡要求的簽名與加密處理,但是 Metro/WSIT 1.5 卻沒有,結果造成客戶端出錯。這是 WSIT 代碼中的錯誤,在下一個版本的 Metro 中應該會得到更正。
如果您運行版本低一些的 Axis2/Rampart,您可能不會遇到任何問題 — 因為 Rampart 直到 1.5 版 本才出現了這個 bug。
結束語
Metro 對 WS-SecurityPolicy 的 WSIT 支持既允許直接配置用戶名和密碼等參數(包括密鑰存儲和私 有密鑰密碼),也可以根據需要通過回調來設置這些值。它還允許您選擇使用 servlet 容器的授權處理 機制或者您自己的回調來在服務器上驗證用戶名和密碼組合。這種靈活性使得 Metro 能夠輕松滿足各種 類型的應用程序的需要。Metro 通過多個集成組件提供 WSIT/XWSS WS-Security 支持,而不是像 Axis2 和 Rampart 那樣使用一個獨立的組件(擁有自身的發布周期,且不同版本的核心組件之間通常不兼容) 。
不足之處是,關於單獨使用與直接配置 Metro/WSIT 的信息非常稀少(相對於與 NetBeans IDE 和 Glassfish 應用程序服務器聯合使用的相關信息)。許多必須的信息僅僅在博客文章和電子郵件中進行記 錄。
接下來的 Java Web 服務 文章將繼續討論 Metro,下次的關注點是它的性能。與 Axis2 相比,Metro 在簡單的消息交換中和使用 WS-Security 時的性能如何。
本文配套源碼