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

Java Web服務 - Axis2 WS-Security簽名和加密

編輯:關於JAVA

安全性對於使用 Web 服務交換業務數據至關重要。如果數據被第三方截取,或者欺騙性數據被當作有效數據接收,那麼會引起嚴重的財務或法律後果。為 Web 服務(任何形式的數據交換)設計和實現應用程序的安全處理始終是可行的,但是這種方法比較冒險,因為即使是一個小錯誤和疏漏都會導致嚴重的安全漏洞。與其他更簡單的數據交換相比,SOAP 的優勢之一是它支持模塊化擴展。自 SOAP 首次發布以來,安全性一直是擴展的主要關注點,促使了 WS-Security 和相關技術的標准化,允許針對每一個服務相應地配置安全性。

信息交換對安全性的需求通常包含三個方面:

機密性:只有消息的目標接收者有權訪問消息內容。

完整性:接收到的消息沒有發生任何修改。

真實性:可以對消息的來源進行檢驗。

WS-Security 可以讓您輕松地滿足這三個方面的要求。在本文中,您將理解如何通過使用 Axis2 和 Rampart WS-Security 擴展實現這點。但是,我們首先將扼要概述一下公開密匙加密的原理 — 這是 WS-Security 的加密和簽名特性的主要基礎。

公開密匙加密

縱觀歷史,安全消息交換一直以來都基於某種形式的共享秘密。這個秘密可能采用代碼的形式,交換的雙方使用經過商定的內容替換詞語或操作。或者可以是密碼的形式,通過某種算法將某種文本轉換為其他文本。秘密甚至可以采用其他形式,比如對於可能訪問消息的其他方未知的語言。共享秘密可以使消息交換非常安全。但是,如果其他方發現了這個秘密,那麼消息交換就會洩漏,並會為消息交換方帶來潛在的破壞性結果。(比如,想一想二戰時期 Enigma 和 German 的軍事通信)。

公開密匙加密是一種與其他加密有著本質區別的安全方法,它不需要共享的秘密。它所基於的理念是數學上的 “trap-door” 函數,它可以沿著一個方向輕松地進行計算,但是很難從相反的方向進行計算。比如,可以很容易找到兩個質數的乘積(如果使用電腦的話,甚至可以是非常大的質數),但是很難通過分析乘積來找到原始的兩個因數。如果圍繞某個函數的簡單方向構造一個加密算法,那麼想要破解加密的任何人都需要從相反的方向來解密。和任何精心挑選的算法一樣,破解加密的嘗試將非常困難,以至於無法在可以對消息交換產生威脅的時間期限內完成(至少要等到有人開發了可用的量子計算機,或真正有效的特異功能)。

使用了公開密匙加密後,希望接收已加密消息的一方將創建一對密匙值。每個密匙值都可以單獨用於加密消息,但是不能用於解密由它本身加密的消息。相反,這對密匙值的另外一個密匙必須用於解密。只要密匙的所有者將其中的一個密匙保密,另一個密匙就可以公開給他人。任何訪問這個公開密匙的人都可以用它加密消息,而只有密匙所有者才能夠對消息解密。由於使用了不同的密匙進行加密和解密消息,因此這種形式的加密被稱為不對稱加密。

消息簽名

當使用您的公開密匙對消息進行加密,那麼只有您(即私有密匙的持有者)才能夠解密消息。這可以確保機密性,滿足了安全消息交換三個方面的其中一個方面。但是也可以使用您的私有 密匙來加密消息,這樣做之後,任何具有您的公開 密匙的人都可以解密這個消息。乍看上去這似乎沒什麼用 — 任何人都可以讀取的加密消息有什麼用?— 但是這種方法可以很好地充當一種檢驗真實性的機制。據稱從您那裡收到已加密消息的人可以使用您的公共密匙來解密消息並與預期值進行比較。如果值匹配的話,那麼他們就知道是您 創建了這條消息。

在實際中,消息簽名的過程不僅僅是使用私有密匙加密消息。首先,您需要使用某種方法為解密消息確立預期值。這通常使用另一種經過變化的數學 trap-door 函數完成:這是一個散列(hash)函數,它易於計算但難以復制(即很難在不修改該消息的散列值的情況下對消息進行修改,或者很難找到具有相同散列值的另一個消息作為提供的消息)。使用這種散列函數,可以為希望簽名的消息生成散列值(在安全討論中通常稱為一個摘要(digest)),然後使用私有密匙加密該摘要並使用消息發送已加密的摘要值。任何接收此消息的人都可以對該消息使用相同的散列算法,然後使用您的公開密匙解密附帶的已加密摘要,並比較兩者的值。如果值匹配的話,那麼接收方可以確定(在當前技術的范圍內,並認為您一直對您的私有密匙保密)該消息是由您發送的。

在處理 XML 時,消息簽名過程還涉及到另一個步驟。XML 消息以文本形式表示,但是文本表示的某些方面被 XML 認為是無關的(比如元素上的屬性的順序,或元素開始和結束標記內使用的空白)。由於這個與文本表示有關的問題,因此 W3C(XML 規范的所有者)決定在計算摘要值之前,將 XML 消息轉換為一個標准的文本形式。一些標准化算法也得到定義,可以用於 XML 消息。只要消息交換的雙方都同意使用相同的算法,那麼具體使用哪種算法並不會產生什麼影響。

使用經過簽名的消息摘要可以同時保證消息完整性(因為對消息的修改將會改變摘要值)和真實性(因為您的私有密匙被用於加密摘要)。由於使用公開密匙的加密可以確保消息的私密性,因此消息交換安全性的所有主要方面都可以通過使用一個公開-私有密匙對涵蓋。當然,對於一個密匙對,消息交換中只有一方是安全的 — 但是如果交換的另外一方也具有自己的公開/私有密匙對,那麼就可以為消息交換的雙方提供充分的安全性。

證書

公開-私有密匙對可以同時用於對雙方之間交換的信息進行加密和簽名,假設交換雙方都可以訪問對方的公開密匙。這產生了一個問題,如何在保證安全性的情況下獲得公開密匙。實現此目的的方法有許多種,其中最常用的是對公開密匙使用一個或多個可靠的第三方證明。數字證書 就是一種以第三方證明的形式提供公開密匙的機制。

數字證書實質上就是一個包裝公開密匙的包裝器,其中包含了用於密匙所有方的識別信息。然後由一個可信的第三方簽名被封裝的內容,並且簽名被包括到證書中。可信的第三方通過使用其自己的簽名發出證書,為公開密匙和識別信息做證明。當然,這又擺出了如何確立可信第三方的身份的自舉問題。通常的解決方法是將某些稱為發證機關(issuing authority)的可信第三方的證書硬編碼到軟件(比如 JVM)中。

除了這裡描述的基本內容外,數字證書還涉及到很多其他內容,包括由於發生錯誤或洩漏私有密匙而需要撤回發出的證書,證書的有效期限,以及指定證書既定用途的擴展。還可以查閱文檔,獲得 JDK 安裝中附帶的 keytool 安全工具。keytool 文檔很好地介紹了證書結構和處理,以及 keystores(稍後討論)和其他安全性內容。您還將在本文後面看到一個使用 keytool 的示例。

Keystores

大多數 Java 安全代碼都使用 keystore 來處理私有密匙和數字證書。一個 keystore 實際上就是一個以加密形式包含密匙和證書條目的文件。需要使用密碼才能訪問 keystore。keystore 中的每個私有密匙會被再一次加密,需要額外的密碼才能保持密匙的安全性。使用 keystore 和私有密匙的任何軟件都需要在運行時保持 keystore 和私有密匙密碼可用。這限制了由這些密碼提供的安全性(因為任何訪問源代碼的人都可以了解到密碼的載入方式)。因此您需要維護在其中托管了軟件以及任何系統備份的系統的物理安全性。並且,要保護私有密匙的安全性,只能將 keystore 和密碼放到該系統和系統備份中。

秘密密匙和對稱加密

盡管使用非對稱加密的公開密匙加密構成了許多有用 WS-Security 特性的基礎,但是老式的秘密密匙加密仍然發揮著重要的作用。要實現相同程度的保護,非對稱加密算法往往要比基於秘密密匙(同一個密匙同時用於加密和解密,意味著這個密匙必須始終保密)的對稱算法涉及更多的計算量。由於這個原因,經常將這兩種技術結合使用:高開銷的非對稱加密用於保障秘密密匙交換的安全性,秘密密匙然後又可用於低開銷的對稱加密。本文後面介紹 WS-Security 消息加密時,您將看到一個這樣的示例。

設置

本文使用了與 “Axis2 WS-Security 基礎” 相同的示例應用程序,該應用程序展現了如何使用 Axis2 和 Rampart 實現 WS-Security UsernameToken。但是,需要作出一些修改來支持使用 WS-Security 公開密匙加密特性,因此本文附帶了一個單獨的代碼包(見 下載 )。

示例代碼的根目錄為 jws05code。該目錄包含了以下內容:

一個 Ant build.xml 文件

一個 Ant build.properties 文件,它配置了示例應用程序的操作

library.wsdl 文件為示例應用程序提供了服務定義

log4j.properties 文件用於配置客戶端登錄

一些屬性定義 XML 文件(所有名為 XXX-policy-client.xml 或 XXX-policy-server.xml 的文件)

在開始使用示例代碼前,需要完成以下操作:

編輯 build.properties 文件,設置 Axis2 安裝的路徑。

確保 Axis2 安裝已經通過 Rampart 代碼進行了更新,如 “Axis2 WS-Security 基礎” 的 安裝 Rampart 一節所述。(一種好方法就是查看 repository/modules 目錄中的 rampart-x.y.mar 模塊文件)。

將 org.bouncycastle.jce.provider.BouncyCastleProvider Bouncy Castle 安全提供者(用於示例代碼中使用的公開密匙加密特性)添加到 JVM 安全配置中(lib/security/java.security 文件)。

將 Bouncy Castle JAR(名為 bcprov-jdkxx-vvv.jar,其中 xx 表示 Java 版本,vvv 表示 Bouncy Castle 代碼版本)同時 添加到 Axis2 安裝的 lib 目錄和 Axis2 服務器應用程序的 WEB-INF/lib 目錄。

現在,您已經准備好構建示例應用程序並嘗試下一節將介紹的安全示例。

對消息進行簽名

簽名要比 “Axis2 WS-Security 基礎” 中的 UsernameToken 示例用到更多規范。您需要:

識別用於在每個方向上創建簽名的私有/公開密匙對,提供用於訪問 keystore 和私有密匙的密碼。

指定用於 XML 標准化、摘要生成和實際簽名的算法集。

指定將消息的哪些部分包含到簽名中。

這些信息中的一部分被作為配置數據處理,被嵌入到服務的 WS-SecurityPolicy 文檔中。其他部分被包含到運行時消息交換中。

清單 1 展示了一個 WS-Policy 文檔,用於將 Axis2 客戶機配置為對消息使用簽名。(清單 1 進行了編輯,以符合頁面的寬度。在示例代碼中可以查看 sign-policy-client.xml 的完整文本)。

清單 1. 用於簽名(客戶機)的 WS-Policy/WS-SecurityPolicy

<!-- Client policy for signing all messages, with certificates included in each 
  message -->
<wsp:Policy wsu:Id="SignOnly"
   xmlns:wsu="http://.../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="http://.../IncludeToken/AlwaysToRecipient"/>
       </wsp:Policy>
      </sp:InitiatorToken>
      <sp:RecipientToken>
       <wsp:Policy>
        <sp:X509Token
          sp:IncludeToken="http://.../IncludeToken/AlwaysToInitiator"/>
       </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://.../ws-securitypolicy/200702">
     <sp:Body/>
    </sp:SignedParts>

    <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>

在 清單 1 中,策略中的 <sp:AsymmetricBinding> 元素給出了在消息交換中使用公開密匙加密的基本配置信息。在這個元素內,使用了一些嵌套元素來提供配置的細節。<sp:InitiatorToken> 識別出用於對由客戶機(發起者)發往服務器(接收者)的消息進行簽名的密匙對,在本例中指定這個公開密匙將采用 X.509 證書的形式並連同來自客戶機的消息一起發送(sp:IncludeToken=".../AlwaysToRecipient")。<sp:RecipientToken> 識別用於對從服務器返回給客戶機的消息進行簽名的密匙對,同樣使用的是 X.509 證書,並且證書被包含在來自服務器的消息中(sp:IncludeToken=".../AlwaysToInitiator")。

<sp:AlgorithmSuite> 元素識別用於進行簽名的一組算法。<sp:IncludeTimestamp> 提供了用於每個消息的時間戳(用於保護服務免受重播式攻擊,在這種攻擊下,消息在傳輸中被捕獲並被重新提交,從而擾亂或中斷服務)。<sp:OnlySignEntireHeadersAndBody> 元素指定簽名將在消息的整個頭部或消息體中完成,而不是在某些嵌套的元素(另一種安全措施,防止發生重寫消息的攻擊類型)中完成。<sp:SignedParts> 元素識別將要被簽名的消息部分 — 在本例中為 SOAP 消息 Body。

清單 1 中的 WS-Policy 文檔的最後一部分提供了特定於 Rampart 的配置信息。這是 “Axis2 WS-Security 基礎” 中使用的 Rampart 配置的更加復雜的版本,這一次包括了一個 <ramp:user> 元素來識別將用於消息簽名的密匙,以及一個 <ramp:signatureCrypto> 元素,用於配置包含客戶機私有密匙和服務器證書的 keystore。被引用的 keystore 文件必須在運行時提供給類路徑。在示例應用程序中,keystore 文件在構建期間被復制到 client/bin 目錄中。

密碼回調類與 “Axis2 WS-Security 基礎” 中使用的類稍有不同。在那篇文章中,只需要在服務器使用密碼回調,並且只需要檢驗(用於明文式 UsernameToken)或設置(用於散列式 UsernameToken)與特定用戶名匹配的密碼。對於本文使用的公開密匙加密,回調必須提供用於保護 keystore 內的私有密匙的密碼。並且需要對客戶機(為客戶機私有密匙提供密碼)和服務器(為服務器私有密匙提供密碼)使用單獨的回調。清單 2 展示了回調的客戶端版本:

清單 2. 客戶機密碼回調

/**
  * Simple password callback handler. This just checks if the password for the private key
  * is being requested, and if so sets that value.
  */
public class PWCBHandler implements CallbackHandler
{
   public void handle(Callback[] callbacks) throws IOException {
     for (int i = 0; i < callbacks.length; i++) {
       WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
       String id = pwcb.getIdentifer();
       int usage = pwcb.getUsage();
       if (usage == WSPasswordCallback.DECRYPT || 
         usage == WSPasswordCallback.SIGNATURE) {

         // used to retrieve password for private key
         if ("clientkey".equals(id)) {
           pwcb.setPassword("clientpass");
         }

       }
     }
   }
}

清單 2 中的回調旨在支持使用同一個公開-私有密匙對進行簽名和解密,因此將在兩種情況下進行檢查並對任意一種情況返回相同的密碼。

清單 1 中的 WS-Policy 用於客戶機,與此相對應的是用於服務器的 WS-Policy(示例代碼中的 sign-policy-server.xml),兩者只在 Rampart 配置的細節方面略有不同。同樣,清單 2 密碼回調類的服務器版本只在標識符值和返回的密碼方面有所差異。

運行示例應用程序

build.properties 文件引用了將由示例應用程序使用的 client.policy 和 server.policy 文件。在文件的補充版中分別被設置為 sign-policy-client.xml 和 sign-policy-server.xml,因此您應當只需要構建應用程序。通過將控制台打開到 jws05code 目錄並輸入 ant,您可以使用 Ant 完成任務。如果所有內容均配置正確,那麼應當在 jws05code 目錄中得到一個 library-signencr.aar Axis2 服務歸檔文件。通過使用 Axis2 Administration 頁面上傳 .aar 文件,將該服務部署到您的 Axis2 服務器安裝中,然後通過在控制台輸入 ant run 來試用客戶機。如果所有內容都正確設置,那麼應當看到如圖 1 所示的輸出:

圖 1. 運行應用程序的控制台輸出

要查看消息中的實際 WS-Security 信息,需要使用 TCPMon 之類的工具。首先設置 TCPMon 並在一個端口上接受來自客戶機的連接,該連接隨後轉發給運行在另一個端口上的服務器(或另一個主機)。隨後可以編輯 build.properties 文件並將 host-port 值修改為 TCPMon 的偵聽端口。如果在控制台中再一次輸入 ant run,應當會看到被交換的消息。清單 3 展示了一個樣例客戶機消息捕捉:

清單 3. 從客戶機發送給服務器的第一條消息

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header>
   <wsse:Security xmlns:wsse=".../oasis-200401-wss-wssecurity-secext-1.0.xsd"
     soapenv:mustUnderstand="1">
    <wsu:Timestamp xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
      wsu:Id="Timestamp-3753023">
     <wsu:Created>2009-04-18T19:26:14.779Z</wsu:Created>
     <wsu:Expires>2009-04-18T19:31:14.779Z</wsu:Expires>
    </wsu:Timestamp>
    <wsse:BinarySecurityToken
      xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
      EncodingType=".../oasis-200401-wss-soap-message-security-1.0#Base64Binary"
      ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"
      wsu:Id="CertId-2650016">MIICoDC...0n33w==</wsse:BinarySecurityToken>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
      Id="Signature-29086271">
     <ds:SignedInfo>
      <ds:CanonicalizationMethod
        Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
      <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
      <ds:Reference URI="#Id-14306161">
       <ds:Transforms>
        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
       </ds:Transforms>
       <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
       <ds:DigestValue>SiU8LTnBL10/mDCPTFETs+ZNL3c=</ds:DigestValue>
      </ds:Reference>
      <ds:Reference URI="#Timestamp-3753023">
       <ds:Transforms>
        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
       </ds:Transforms>
       <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
       <ds:DigestValue>2YopLipLgBFJi5Xdgz+harM9hO0=</ds:DigestValue>
      </ds:Reference>
     </ds:SignedInfo>
     <ds:SignatureValue>TnUQtz...VUpZcm3Nk=</ds:SignatureValue>
     <ds:KeyInfo Id="KeyId-3932167">
      <wsse:SecurityTokenReference
        xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
        wsu:Id="STRId-25616143">
       <wsse:Reference URI="#CertId-2650016"
        ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"/>
      </wsse:SecurityTokenReference>
     </ds:KeyInfo>
    </ds:Signature>
   </wsse:Security>
  </soapenv:Header>
  <soapenv:Body
    xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Id-14306161">
   <ns2:getBook xmlns:ns2="http://ws.sosnoski.com/library/wsdl">
    <ns2:isbn>0061020052</ns2:isbn>
   </ns2:getBook>
  </soapenv:Body>
</soapenv:Envelope>

SOAP 消息中的 <wsse:Security> 頭部保存有所有運行時安全配置信息和簽名數據。第一個項是 <wsu:Timestamp>,在 WS-SecurityPolicy 配置中被請求。時間戳包含了兩個時間值:創建時間和過期時間。這兩個值在本例中為 5 分鐘的時間間隔,這是 Rampart 的默認設置。(可以在 Rampart 策略配置中修改這兩個值)。這兩個值之間的時間量稍微有些隨意,但是 5 分鐘是一個比較合理的值 — 足夠處理客戶機與服務器之間存在的適當的時鐘偏移(系統時鐘之間的差異),但是同時也能夠限制利用消息進行重播攻擊。在時間戳之後,安全頭部的下一個項是 <wsse:BinarySecurityToken>。這個安全令牌是一個客戶機證書,采用 base64 二進制編碼形式。

安全頭部中的第三個項是一個 <ds:Signature> 塊,其中包含三個子元素。第一個子元素 <ds:SignedInfo> 是直接進行簽名的消息部分。<ds:SignedInfo> 中的第一個子元素標識用於自身標准化和簽名的算法。後面是一個 <ds:Reference> 子元素,針對簽名中包含的每個消息組件。每個子 <ds:Reference> 元素根據標識符引用一個特定消息組件並提供應用到該組件的標准化和摘要算法,以及生成的摘要值。<ds:SignedInfo> 的其余子元素提供了實際的簽名值和一個用於檢驗簽名的公開密匙的引用(在本例中為此前在頭部中的 <wsse:BinarySecurityToken> 中包含的證書,由 wsu:Id="CertId-2650016" 標識)。

加密和簽名消息

向已簽名的消息進行加密非常簡單,只需要向策略添加一個 <sp:EncryptedParts> 元素來標識將被加密的組件,以及添加一些額外的 Rampart 配置信息。清單 4 展示了針對這一用途的策略的客戶機版本(為了適合頁面寬度再次進行了編輯 — 查看示例代碼中的 signencr-policy-client.xml 文件獲得完整文本),其中在 清單 1 策略的基礎上新添的內容以粗體顯示:

清單 4. 用於簽名和加密(客戶機)的 WS-Policy/WS-SecurityPolicy

<!-- Client 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://.../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="http://.../IncludeToken/AlwaysToRecipient"/>
       </wsp:Policy>
      </sp:InitiatorToken>
      <sp:RecipientToken>
       <wsp:Policy>
        <sp:X509Token
          sp:IncludeToken="http://.../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://.../200702">
     <sp:Body/>
    </sp:SignedParts>
    <sp:EncryptedParts xmlns:sp="http://.../200702">
     <sp:Body/>
    </sp:EncryptedParts>

    <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
     <ramp:user>clientkey</ramp:user>
     <ramp:encryptionUser>serverkey</ramp:encryptionUser>
     <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:encryptionCrypto>
      <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:encryptionCrypto>

    </ramp:RampartConfig>

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

僅使用加密不行嗎?

如果可以提供一個僅對 Axis2 和 Rampart 使用加密的示例會很不錯,但是這個功能在 Axis2 1.4(及更早版本)中被去掉了。1.5 發行版提供了一個代碼補丁。如果您使用的是 Axis2 1.5 或更高版本(以及相應的 Rampart 發行版),那麼可以嘗試使用 encr-policy-client.xml 和 encr-policy-server.xml 策略文件來在不使用任何簽名的情況下對每個消息體進行加密。

使用加密不需要 用到 清單 4 策略中的第一處修改,但這是個好主意。在使用加密時,客戶機在發送初始請求時需要用到服務器證書(因為要使用證書中的服務器公開密匙進行加密)。由於客戶機需要具有服務器證書,並不表示要將服務器證書發送給客戶機。清單 4 策略中經過修改的 <sp:RecipientToken> 反映了這個用例,表示不應發送證書(sp:IncludeToken=".../Never"),而應當使用 thumbprint reference(實際上就是一個證書散列)。thumbprint reference 要比完整的證書更加簡潔,因此使用引用可以減小消息的大小和處理開銷。

實際實現加密的修改是新添的 <sp:EncryptedParts> 元素。這個元素表示將要使用加密,而內容 <sp:Body> 元素表示需要對消息中的 SOAP 消息體進行加密。

清單 4 中新添的 Rampart 配置信息包含一個 <ramp:encryptionUser> 元素和一個 <ramp:encryptionCrypto> 元素,前者為用於加密信息的公開密匙(即證書)分配一個別名,後者指出如何訪問包含證書的 keystore。在示例應用程序中,對用於簽名的私有密匙和用於加密的公開密匙使用了相同的 keystore,因此 <ramp:encryptionCrypto> 元素只不過是對現有的 <ramp:signatureCrypto> 元素進行重命名後的副本。

在運行時,Rampart 需要獲得用於保護私有密匙的密碼,這個私有密匙用於對已加密數據進行解密。前面的密碼回調用於獲得私有密匙密碼(用於實現簽名),如 清單 2 所示,還提供了用於解密的密碼,因此不需要做任何修改。

運行示例應用程序

要嘗試運行使用了簽名和加密的示例應用程序,首先需要編輯 build.properties 文件。將客戶機策略行修改為 client.policy=signencr-policy-client.xml,將服務器策略修改為 server-policy=signencr-policy-server.xml。隨後可以通過運行 ant 重新構建應用程序,將生成的 library-signencr.aar 文件部署到 Axis2 安裝中,然後運行 ant run 來進行嘗試。

清單 5 顯示了依次使用簽名和加密後的請求-消息捕捉,與 清單 3 中以粗體顯示的服務器版本有著明顯的不同:

清單 5. 使用了簽名和加密的消息

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
   xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
  <soapenv:Header>
   <wsse:Security xmlns:wsse=".../oasis-200401-wss-wssecurity-secext-1.0.xsd"
     soapenv:mustUnderstand="1">
    <wsu:Timestamp xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
      wsu:Id="Timestamp-4067003">
     <wsu:Created>2009-04-21T23:15:47.701Z</wsu:Created>
     <wsu:Expires>2009-04-21T23:20:47.701Z</wsu:Expires>
    </wsu:Timestamp>
    <xenc:EncryptedKey Id="EncKeyId-urn:uuid:6E12E251E439C034FA12403557497352">
     <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
     <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
      <wsse:SecurityTokenReference>
       <wsse:KeyIdentifier
         EncodingType="http://...-wss-soap-message-security-1.0#Base64Binary"
         ValueType="http://.../oasis-wss-soap-message-security-1.1#ThumbprintSHA1"
         >uYn3PK2wXheN2lLZr4n2mJjoWE0=</wsse:KeyIdentifier>
      </wsse:SecurityTokenReference>
     </ds:KeyInfo>
     <xenc:CipherData>
      <xenc:CipherValue>OBUcMI...OIPQEUQaxkZps=</xenc:CipherValue>
     </xenc:CipherData>
     <xenc:ReferenceList>
      <xenc:DataReference URI="#EncDataId-28290629"/>
     </xenc:ReferenceList>
    </xenc:EncryptedKey>
    <wsse:BinarySecurityToken
      xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
      EncodingType="http://...-wss-soap-message-security-1.0#Base64Binary"
      ValueType="http://.../oasis-200401-wss-x509-token-profile-1.0#X509v1"
      wsu:Id="CertId-2650016">MIICo...QUBCPx+m8/0n33w==</wsse:BinarySecurityToken>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
      Id="Signature-12818976">
     <ds:SignedInfo>
      <ds:CanonicalizationMethod
        Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
      <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
      <ds:Reference URI="#Id-28290629">
       <ds:Transforms>
        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
       </ds:Transforms>
       <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
       <ds:DigestValue>5RQy7La+tL2kyz/ae1Z8Eqw2qiI=</ds:DigestValue>
      </ds:Reference>
      <ds:Reference URI="#Timestamp-4067003">
       <ds:Transforms>
        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
       </ds:Transforms>
       <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
       <ds:DigestValue>GAt/gC/4mPbnKcfahUW0aWE43Y0=</ds:DigestValue>
      </ds:Reference>
     </ds:SignedInfo>
     <ds:SignatureValue>DhamMx...+Umrnims=</ds:SignatureValue>
     <ds:KeyInfo Id="KeyId-31999426">
      <wsse:SecurityTokenReference
        xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
        wsu:Id="STRId-19267322">
       <wsse:Reference URI="#CertId-2650016"
         ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"/>
      </wsse:SecurityTokenReference>
     </ds:KeyInfo>
    </ds:Signature>
   </wsse:Security>
  </soapenv:Header>
  <soapenv:Body
    xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Id-28290629">
   <xenc:EncryptedData Id="EncDataId-28290629"
     Type="http://www.w3.org/2001/04/xmlenc#Content">
    <xenc:EncryptionMethod
      Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
    <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
     <wsse:SecurityTokenReference
       xmlns:wsse="http://.../oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <wsse:Reference URI="#EncKeyId-urn:uuid:6E12E251E439C034FA12403557497352"/>
     </wsse:SecurityTokenReference>
    </ds:KeyInfo>
    <xenc:CipherData>
     <xenc:CipherValue>k9IzAEG...3jBmonCsk=</xenc:CipherValue>
    </xenc:CipherData>
   </xenc:EncryptedData>
  </soapenv:Body>
</soapenv:Envelope>

第一個不同是在安全頭部中出現了 <xenc:EncryptedKey> 元素。這個元素的內容提供了一個加密了的秘密密匙,使用了服務器的公開密匙執行加密。第二個不同之處在於實際的 SOAP Body 內容,它被替換為 <xenc:EncryptedData> 元素。這個已加密的數據從安全頭部引用 <xenc:EncryptedKey> 值作為在 Body 內容中用於對稱加密的密匙。

使用自己的自簽名證書

要獲得由權威認證機構簽名的官方數字證書,需要生成一個公開-私有密匙對並使用公開密匙生成證書請求。然後將證書請求發送給您選擇的機構並支付費用。證書機構將驗證您的身份(保證整個過程的完整性的重要一步,但是可能會出現錯誤),並發出具有其簽名的證書。

如果用於測試或內部用途,可以生成您自己的自簽名證書。本文的示例代碼使用了兩個這種自簽名證書,一個用於客戶機,一個用於服務器。用於客戶端的 client.keystore keystore 包含了客戶機的私有密匙和證書,以及服務器證書(必須存儲在客戶機,這樣在用於簽名時,無需證書機構的簽名就可以被作為有效證書接受,並且也可以直接用於加密)。用於服務器端的 server.keystore keystore 包含了服務器的私有密匙和證書,以及客戶機證書(這樣證書就可以作為有效證書被接受)。

您可以生成自己的私有密匙和自簽名證書,並使用自己生成的密匙-證書對替換下載中的相應內容。要使用 JDK 附帶的 keytool 程序實現替換,請打開控制台並輸入以下命令代碼(在這裡將代碼分解成多個行以適應頁寬;您在輸入時必須作為單一行輸入):

keytool -genkey -alias serverkey -keypass serverpass -keyalg RSA -sigalg SHA1withRSA
   -keystore server.keystore -storepass nosecret

上面的命令生成了別名為 serverkey 的服務器密匙和證書,保存在名為 server.keystore 的新 keystore 中。(如果在此目錄中已經有一個 server.keystore,那麼首先需要刪除使用該別名的現有密匙對)。keytool 提示了許多用於生成認證的信息項(這些信息都不會影響測試使用)並要求您確認這些信息。通過輸入 yes 確認後,keytool 將使用私有密匙和證書創建 keystore,隨後退出該程序。

接下來,再次運行這個程序來生成客戶機密匙對和 keystore,這一次使用以下的命令(以單行形式輸入):

keytool -genkey -alias clientkey -keypass clientpass -keyalg RSA -sigalg SHA1withRSA
   -keystore client.keystore -storepass nosecret

下一步是從服務器 storekey 導出證書,並將證書導入到客戶機 keystore。要執行導出,使用下面的命令(為適應頁面寬度而進行了分解;您必須以單行形式輸入):

keytool -export -alias serverkey -keystore server.keystore -storepass nosecret
   -file servercert.cer

導出創建了一個名為 servercert.cer 的證書文件,您可以將它導入到客戶機 keystore 中,使用以下命令(以單行形式輸入):

keytool -import -alias serverkey -keystore client.keystore -storepass nosecret
   -file servercert.cer

運行導入命令時,keytool 輸出了證書的細節並詢問您是否信任該證書。當您通過輸入 yes 接受密匙後,它將把證書添加到 keystore 並退出。

對於最後一步,導出客戶機證書並將其導入到服務器 server keystore,輸入以下命令(以單行形式輸入):

keytool -export -alias clientkey -keystore client.keystore -storepass nosecret
   -file clientcert.cer

然後運行以下命令(為適應頁面寬度而分行;您必須以單行形式輸入):

keytool -import -alias clientkey -keystore server.keystore -storepass nosecret
   -file clientcert.cer

為什麼要導出/導入兩種證書?

文本要求您為雙方導出一個證書,然後將證書導入到另一方的 keystore。如果您使用了加密,那麼需要為服務器證書執行這一操作,即使證書是由權威機構簽名,因為加密需要訪問另一方的公開密匙。另一方面,對於客戶機證書,只需要將證書導入服務器 keystore,因為證書是自簽名的並且無法通過其他方式驗證。通過將證書導入到 keystore,您已經實現對其進行了確認,因此不需要再通過權威機構進行檢驗。

可以采用相同的辦法處理多個使用自簽名證書的客戶機,只需要將每個客戶機的證書導入到服務器 keystore。作為一種替代選擇,與使用自簽名證書不同的是,可以運行您自己的證書機構(使用 OpenSSL 之類的工具)並要求每個客戶機獲得由該機構簽名的證書。通過這個方式,您可以向服務器 keystore 添加證書機構,並且任何具有由該機構簽名的證書的客戶機將被接受。或者通過支付費用來使用由權威機構簽名的官方證書。

要利用新的密匙和證書,在運行客戶機構建之前,必須將 client.keystore 文件復制到示例代碼的 client/src 目錄(或者只需將其復制到 client/bin 目錄以立即生效),並在運行服務器構建之前將 server.keystore 文件復制到示例代碼的 server/src 目錄。

本節中的樣例 keytool 命令使用了與附帶的示例代碼相同的文件名和密碼。當您生成自己的密匙和證書時,您也可以修改這些值,但是那樣做的話還需要修改示例代碼以便匹配。在消息傳遞雙方的策略文件中,keystore 密碼和文件名都是 RampartConfig 中的參數。客戶機密匙密碼被硬編碼到 com.sosnoski.ws.library.adb.PWCBHandler 類的客戶機版本中,而服務器密匙密碼則位於同一個類的服務器版本中。

結束語

在本文中,您了解了如何使用 Axis2 和 Rampart 實現基於策略的 WS-Security 加密和簽名。這些強大的安全特性對於許多業務數據交換都至關重要,但是它們在處理開銷方面會增加成本。在下一期 Java Web 服務 文章中,我們將介紹各種安全方法的相關性能開銷情況,使您能夠更好地判斷如何在自己的應用程序中使用安全性。

本文配套源碼

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