程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java Web服務: WS-Security的細粒度使用

Java Web服務: WS-Security的細粒度使用

編輯:關於JAVA

在簡單 Web 服務環境中,客戶機直接連接到服務器,而服務器直接對請求執行所有必需的處理。正如本系列 上一篇文章 所述,使用 SSL 提供保護的連接可以為這類環境中的大部分應用提供出色的安全性。但是,更加復雜的環境變得越來越普遍,其中涉及到使用多層服務器處理請求。在許多企業環境中日益流行的服務編排的完整理念就是以這種方法為基礎的,這與面向服務架構(SOA)的概念相同。在這些類型的環境中,必須實現更強大的 WS-Security 替代方案。

正如上一期文章所述,WS-Security 帶來了沉重的性能代價。降低成本的方法之一就是針對由服務定義的各個操作甚至是消息設置合適的 WS-SecurityPolicy,而不是將單個 WS-Security 策略應用到整個服務。WS-Security 的細粒度使用要求考慮更多的內容,而不是使用一成不變的方法,但是如果得到正確應用的話,那麼就可以減少常用操作的性能開銷,同時不會減弱需要 WS-Security 的操作的安全性。

定義策略

本文使用的樣例策略與 “Axis2 WS-Security 基礎” 和 “Axis2 WS-Security 簽名和加密” 中的策略相同 — 一個簡單的庫管理服務。這個服務定義了三種操作:

getBook,檢索由 International Standard Book Number (ISBN) 標識的特定圖書的細節。

getBooksByType,檢索某一類型的所有圖書的細節。

addBook,向庫中添加一本新書。

為了向安全用例中添加一些有趣的變化,本文作出以下假設:

getBook 操作可以安全地公開給任何人(未應用安全性)。

getBooksByType 需要授權(因此要使用 UsernameToken)。

addBook 操作需要一個審計跟蹤,以跟蹤是誰添加了圖書(通過對請求消息進行簽名實現)。

在早期文章中,您已經了解了如何配置 Axis2/Rampart:將一個 WS-SecurityPolicy 文檔連接到 org.apache.axis2.client.ServiceClient 實例(在客戶機端),或者將策略文檔嵌入到 services.xml 服務配置(在服務器端)。這個方法可以奏效,並且可用於測試,但是對於生產應用來說,最好通過將 WS-SecurityPolicy 內嵌到 WSDL 文檔將其直接關聯到服務定義。WS-Policy 和 WS-SecurityPolicy 旨在為這種嵌入提供支持,並且使用來自 <wsdl:binding>、<wsdl:binding>/<wsdl:operation> 或 <wsdl:binding>/<wsdl:operation>/<wsdl:message> 定義的引用標識將被應用到綁定、操作或消息的相應策略。Axis2 1.4.1 實現對內嵌在 WSDL 中的策略的初始處理,而這個實現在當前的 Axis2 1.5 發行版代碼中得到了改進。為了演示策略在 WSDL 中的使用,本文結合使用了 Axis2 1.5 發行版代碼和尚未發行的最新 Rampart 代碼(後者最終會作為 Rampart 1.5 發布)。

清單 1 展示了示例應用程序的 WSDL,其中添加了策略,並在相應的位置中引用。(清單 1 針對長度和寬度進行了編輯;完整的 WSDL 可以從 代碼下載 中的 library.wsdl 文件獲得)。每個策略定義了一個 Id 值,該值隨後從相應的操作(針對 UsernameToken 策略)或消息(針對簽名策略)中引用,以粗體顯示所有策略。

清單 1. 具有細粒度安全策略的 WSDL<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/">

  <!-- Policy for signing message, with certificate from client included in each
   message to server -->
  <wsp:Policy wsu:Id="SignOnly" 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"/>
        </wsp:Policy>
       </sp:InitiatorToken>
       ...
      </wsp:Policy>
     </sp:AsymmetricBinding>
     <sp:SignedParts
       xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
      <sp:Body/>
     </sp:SignedParts>

    </wsp:All>
   </wsp:ExactlyOne>
  </wsp:Policy>

  <!-- Policy for UsernameToken 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:binding name="LibrarySoapBinding" type="wns:Library">

   <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:operation name="getBooksByType">
    <wsp:PolicyReference
      xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
      URI="#UsernameToken"/>
    <wsdlsoap:operation soapAction="urn:getBooksByType"/>
    <wsdl:input name="getBooksByTypeRequest">
     <wsdlsoap:body use="literal"/>
    </wsdl:input>
    <wsdl:output name="getBooksByTypeResponse">
     <wsdlsoap:body use="literal"/>
    </wsdl:output>
   </wsdl:operation>

   <wsdl:operation name="addBook">
    <wsdlsoap:operation soapAction="urn:addBook"/>
    <wsdl:input name="addBookRequest">
     <wsp:PolicyReference
       xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
       URI="#SignOnly"/>
     <wsdlsoap:body use="literal"/>
    </wsdl:input>
    <wsdl:output name="addBookResponse">
     <wsdlsoap:body use="literal"/>
    </wsdl:output>
   </wsdl:operation>

  </wsdl:binding>
  <wsdl:service name="library-granular">
   ...
  </wsdl:service>
</wsdl:definitions>

清單 1 中的策略和 WSDL 均來自早期文章,不過沒有按照本文的方式事先合並。但是,您會發現策略中有一處顯著的不同:早期的版本都包括了 Rampart 配置信息,這些信息特定於客戶機或服務器。現在策略被嵌入到 WSDL 中,因此不適合直接包含 Rampart 配置。(您需要編輯 WSDL 以包含客戶機 Rampart 配置信息,並在每次發生更改時重新生成代碼,而在服務器端,Rampart 配置將被公開給訪問 WSDL 的任何人)。因此示例代碼將單獨設置配置信息。為此,對包含的 Rampart 配置使用了之前用於策略的相同技巧的不同變體。

客戶端使用

不管是否包含 WS-Policy,從用戶的角度來看,從 WSDL 中生成代碼的效果是相同的。如果查看由包含 WS-Policy 的 WSDL 生成的客戶機存根的內部,將發現策略在構建時被直接連接到服務描述的組件中,但是這被隱藏在實現的內部,不會影響到客戶機代碼使用的接口方法。

要在客戶機中利用 WS-SecurityPolicy 配置,您一定 需要在客戶機代碼中采取一些操作。您至少需要在與您的存根實例相關聯的 org.apache.axis2.client.ServiceClient 上使用 Rampart 模塊。這一步是必須的,即使您在 WSDL 中包含了 Rampart 配置信息。不幸的是,在當前的代碼中,似乎沒有任何方法可以在操作或消息級別使用 Rampart,因此目前與 Axis2 客戶機結合使用時,細粒度 WS-Security 的一部分好處就丟失了。

如果將 Rampart 配置與 WSDL 分離,正如建議的那樣,您也需要對服務描述應用這些配置。清單 2 展示了用於本樣例應用程序的客戶機代碼。它調用 applyPolicy() 方法來將包含 Rampart 配置的策略添加到服務器定義。

清單 2. 配置客戶端操作// create the client stub
String target = args[0] + "://" + args[1] + ":" + args[2] + args[3];
System.out.println("Connecting to " + target);
LibraryGranularStub stub = new LibraryGranularStub(target);

// configure and engage rampart module
ServiceClient client = stub._getServiceClient();
client.getAxisService().applyPolicy(loadPolicy("rampart-client-policy.xml"));
client.engageModule("rampart");

// set the username and password for requests which use them
Options options = client.getOptions();
options.setUserName("libuser");
options.setPassword("books");

清單 2 中的代碼根據 ServiceClient 選項設置用戶名和密碼,意味著這些是針對使用該服務的所有操作定義的,即使它們只用於單個操作。與將 Rampart 模塊用於所有操作不同的是,以這種方式設置用戶名和密碼是無害的 — 這些值只有在 Rampart 需要用之構建 UsernameToken 時使用,其他情況下將會忽略。

清單 3 展示了包含有 Rampart 配置的策略文檔。這裡使用了與 “Axis2 WS-Security 簽名和加密” 相同的策略,現在被提取到一個獨立的策略文檔中。

清單 3. Rampart 客戶機配置策略<wsp:Policy 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>

    <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
     <ramp:user>clientkey</ramp:user>
     <ramp:passwordCallbackClass
      >com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>

     <ramp:signatureCrypto>
      <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
       <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
        >JKS</ramp:property>
       <ramp:property name="org.apache.ws.security.crypto.merlin.file"
        >client.keystore</ramp:property>
       <ramp:property
        name="org.apache.ws.security.crypto.merlin.keystore.password"
        >nosecret</ramp:property>
      </ramp:crypto>
     </ramp:signatureCrypto>

    </ramp:RampartConfig>

   </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

代碼下載中包含的 Ant build.xml 將處理從 WSDL 生成客戶機和服務器代碼、編譯代碼,以及生成服務 AAR 文件。它還會將 Rampart 配置策略(即下載中的 rampart-client-policy.xml,位於根目錄)復制到客戶機類路徑,同時執行一些服務器端策略處理,下節將進行討論。

服務器端使用

如果希望將服務器端 Rampart 配置與 WSDL 分離(通常是一個很不錯的主意!),需要將它包含到 services.xml 文件中。在早期文章中我們執行了這個操作,以獲得包括 Rampart 配置的完整 WS-SecurityPolicy 配置,並作為一個整體應用到服務。這一次,Rampart 配置策略是被添加到 services.xml 的唯一一個部分,並且是在操作級別完成的。

清單 4 展示了包含有 Rampart 配置的策略文檔。與客戶端的對應策略相同,這裡使用了與 “Axis2 WS-Security 簽名和加密” 相同的策略,現在被提取到一個獨立的策略文檔中。

清單 4. Rampart 服務器配置策略<wsp:Policy 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>

    <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
     <ramp:user>serverkey</ramp:user>
     <ramp:passwordCallbackClass
      >com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>

     <ramp:signatureCrypto>
      <ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
       <ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
        >JKS</ramp:property>
       <ramp:property name="org.apache.ws.security.crypto.merlin.file"
        >server.keystore</ramp:property>
       <ramp:property
        name="org.apache.ws.security.crypto.merlin.keystore.password"
        >nosecret</ramp:property>
      </ramp:crypto>
     </ramp:signatureCrypto>

    </ramp:RampartConfig>

   </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

Ant build.xml 使用了一個 PolicyTool 程序(包含在代碼下載中)將 清單 4 中的策略(即下載中的 rampart-client-policy.xml,位於根目錄)合並到 services.xml。修改過的 services.xml 如 清單 5 所示(針對寬度和長度進行了編輯):

清單 5. 合並後的 services.xml<serviceGroup>
  <service name="library-granular">
   <messageReceivers>
    <messageReceiver class=
     "com.sosnoski.ws.library.adb.LibraryGranularMessageReceiverInOut"
     mep="http://www.w3.org/ns/wsdl/in-out"/>
   </messageReceivers>
   <parameter name="ServiceClass"
    >com.sosnoski.ws.library.adb.LibraryGranularImpl</parameter>
   <parameter name="useOriginalwsdl">true</parameter>
   <parameter name="modifyUserWSDLPortAddress">true</parameter>
   <operation mep="http://www.w3.org/ns/wsdl/in-out" name="getBook"
    namespace="http://ws.sosnoski.com/library/wsdl">
    <actionMapping>urn:getBook</actionMapping>
    <outputActionMapping>.../getBookResponse</outputActionMapping>
   </operation>
   <operation mep="http://www.w3.org/ns/wsdl/in-out" name="getBooksByType"
    namespace="http://ws.sosnoski.com/library/wsdl">
    <actionMapping>urn:getBooksByType</actionMapping>
    <outputActionMapping>.../getBooksByTypeResponse</outputActionMapping>

<module ref="rampart"/>
<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">
  <wsp:ExactlyOne>
  <wsp:All>

   <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
   <ramp:user>serverkey</ramp:user>
   ...

   </ramp:RampartConfig>

  </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>
  </operation>
   <operation mep="http://www.w3.org/ns/wsdl/in-out" name="addBook"
   namespace="http://ws.sosnoski.com/library/wsdl">
    <actionMapping>urn:addBook</actionMapping>
    <outputActionMapping>
     http://ws.sosnoski.com/library/wsdl/Library/addBookResponse
    </outputActionMapping>

<module ref="rampart"/>
<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">
  <wsp:ExactlyOne>
  <wsp:All>

   <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
   <ramp:user>serverkey</ramp:user>
   ...

   </ramp:RampartConfig>

  </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>
  </operation>
  </service>
</serviceGroup>

在 清單 5 中,可以看到一個 Rampart 模塊引用和一個服務器配置策略的副本被添加到使用 WS-Security 的兩個操作的定義中,這兩個操作分別為 getBooksByType 和 addBook。這些修改將服務配置為只針對這兩個操作使用 Rampart,並對 Rampart 的鍵和證書訪問使用提供的設置。

如果構建生成的 AAR 文件並部署到一個 Axis2 服務器安裝中,可以在 Administration/Available Services 頁面中看到這些操作的結果,在這個頁面中,將列出由服務定義的每個操作使用的模塊。如果運行客戶機代碼並監視消息交換(使用 TCPMon 之類的工具,像 “Axis2 WS-Security 基礎” 中描述的那樣),您會看到安全處理將按照我們的期望運行:

getBook 消息中不存在 WS-Security 頭部

getBooksByType 請求消息中有一個包含 UsernameToken 的 WS-Security 頭部

addBook 請求(而不是響應)消息中有一個包含簽名的 WS-Security 頭部

Axis2/Rampart 限制

Axis2 和 Rampart 目前(也就是說,使用最新的 Axis2 1.5 發行版和一個 Rampart 夜間構建版)能夠處理 WSDL 中內嵌的基本 WS-SecurityPolicy 配置,包括在操作或消息級別定義的粒度策略。然而,本文嘗試的一些配置無法用於 Axis2/Rampart。例如,嘗試為 addBook 操作設置 UsernameToken 策略(除了對輸入消息使用簽名策略外),結果將導致一個明顯與 Rampart 代碼加密有關的錯誤(即使沒有使用任何加密)。從這些經驗以及一些問題報告中可以判斷出,Axis2/Rampart WS-SecurityPolicy 處理適合用於可以反映常見用例的簡單配置,但是當應用到不常見的組合時,卻容易出現錯誤。

另一個問題是,在服務器上使用 Rampart 產生的開銷始終會出現在請求和響應處理中,即使您只對其中一條消息應用 WS-Security(像 “WS-Security 的大開銷” 所討論的那樣)。在客戶端,情況更糟糕 — 因為在客戶機上使用 Rampart 目前是一種全有或全無的主張,如果在任何 操作上使用 WS-Security,那麼不得不忍受每個 操作中產生的 Rampart 開銷。

最後,目前的 Axis2/Rampart WS-Policy 處理並不支持策略替代方案。替代選擇是 WS-Policy 設計的一個主要特點,使客戶機在訪問服務時可以從兩個或多個可行配置中選擇要使用的配置。Axis2/Rampart 策略實現並不支持這個特性,而是忽略除第一種替代選擇以外的所有選擇。

結束語

通過本文和前三篇文章, Java Web 服務 系列文章介紹了 Axis2/Rampart 中的 WS-Security 和 WS-SecurityPolicy 處理的所有主要方面,包括性能問題。在未來幾期文章中,您將了解其他 Java Web 服務框架如何使用 WS-Security 和 WS-SecurityPolicy,但是首先需要查看 Axis2 的其他兩個方面。

對於許多組織而言,一個重要的問題就是支持 Java Architecture for XML Binding (JAXB) 2.X。JAXB 2.X 是面向 XML 數據綁定的官方 Java 標准,盡管開源替代方案提供了自己獨特的優勢,一些組織仍然堅持使用該標准。在下一期文章中,您將看到如何結合使用 JAXB 2.X 數據綁定和 Axis2,並將它與其他受 Axis2 支持的數據綁定選擇進行比較。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved