利用 Web Services Enhancements 進行 WS-Security 驗證及數位簽章
作者:Matt Powell
Microsoft Corporation
2002 年 12 月
適用於:
Microsoft® ASP.NET
Microsoft® .NET Framework
Microsoft® Visual Studio® 專案
SOAP
Web Services Enhancements 1.0 for Microsoft® .NET
WS-Security 規格
摘要: 如何使用 Web Services Enhancements 1.0 for Microsoft .NET 善用 WS-Security 規格為您的 Web 服務進行驗證及資料簽章。(列印共 30 頁)
您可以下載 Web Services Enhancements 1.0 for Microsoft .NET。
目錄
前言
The WSE 環境
基本的 UsernameToken 驗證
傳送 X.509 憑證作為 WS-Security 權杖
數位簽章
結論
參考資料
前言
由於 Web Services Enhancements 1.0 for Microsoft .NET (WSE) 的出現,Microsoft 提供了它在 SOAP 訊息中實作了安全性功能的第一組工具集。Web 服務不再只能使用基礎傳輸機制所提供的安全性功能。現在一個 SOAP 訊息本身也可以被驗證,查證它的一致性,甚至利用 WS-Security 規格所定義的機制對訊息內容進行加密的處理。在本篇文章中,我們會討論到您如何使用 WSE 來善用 WS-Security 為您的 Web 服務進行驗證和資料簽章的作業。
The WSE 環境
WSE 是架構在 .NET Framework 之上支援 Web 服務的撰寫及使用。在 WSE 核心部份的支援是來自 Microsoft.Web.Services.SoapContext 類別,它提供了一個介面以檢視 WS-Security 標題及其它收到的 SOAP 訊息的標題,並且加入 WS-Security 和其他標題到要傳出的 SOAP 訊息上。對於撰寫程式碼以使用 Web 服務的開發人員而言,會藉由為 SOAP 要求及回應加入 SoapContext 來提供包裝函式類別加強架構的能力。在伺服器端,會建立一個 Microsoft ASP.NET 的 SOAP 延伸驗證收到的 SOAP 訊息並產生一個要求及回應的 SoapContext,它可以在 WebMethod 當中存取。
安排基本的 WSE 環境牽涉到設定您的 ASP.NET 應用程式來使用 WSE SOAP 延伸。這有可能以整台電腦為基礎透過在 Machine.config 加入項目來達成,但是也有可能會碰到不需要 WSE 支援的情況。我們可以將 WSE 支援的能力加到特定的虛擬目錄上。要進行這項設定,請在您的虛擬目錄的 Web.config 檔中加入/configuration/system.web/webServices/soapExtensionTypes/Add 元素。加入的元素看起來像下面的樣子,只是 type 屬性必須要在同一行當中 (這裡為了方便閱讀做了換行的處理)。
<webServices> <soapExtensionTypes> <add type="Microsoft.Web.Services.WebServicesExtension, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" /> </soapExtensionTypes> </webServices>
從 WSE 的觀點來看,我們現在已經做好準備了。如果要使用到 WS-Security 的其他功能,我們還得再額外做一些設定,這部份我們稍後會討論到。現在我們唯一要做的就是在我們的 Visual Studio .NET 專案中加入對 Microsoft.Web.Services.dll 的參照,這樣就好了。
基本的 UsernameToken 驗證
在我們可以 SOAP 訊息進行數位簽章之前,首先需要搞清楚是誰在進行簽章。我們可不會想看到應該對來自張三的訊息進行驗證的工作,卻是在驗證來自李四的訊息。因此,我們會先從瞭解 UsernameTokens 的概念以及 WSE 如何讓我們去進行驗證工作來開始我們對 WS-Security 的討論。
UsernameToken 元素是定義在 WS-Security 中提供進行基本使用者/密碼驗證的工作。如果您對 HTTP 有概念,您可以把它想成是類似「基本驗證」(Basic Authentication) 的概念。WS-Security 透過三種形式的 UsernameToken 元素進行基本的驗證工作。下面是不同的形式:
<!-- No Password --> <UsernameToken> <Username>Bob</Username> </UsernameToken> <!-- Clear Text Password --> <UsernameToken> <Username>Bob</Username> <Password Type="wsse:PasswordText">Opensezme</Password> </UsernameToken> <!-- Digest: SHA1 hash of base64-encoded Password --> <UsernameToken> <Username>Bob</Username> <Password Type="wsse:PasswordDigest"> QSMAKo67+vzYnU9TcMSqOFXy14U= </Password> </UsernameToken>
第一種形式並不包含密碼,所以不妨在有其他的機制驗證個別使用者,而使用者名稱權杖只是用來辨識身份的情況下使用。第二個形式包含了一個純文字密碼。如您所預期的,在驗證過程進行中的另一端會檢查某種資料庫確認使用者名稱和密碼是吻合的。第三種選擇是傳送密碼的摘要,而不是純文字密碼。這種方式的優點是密碼不會直接送上線,所以某些邪惡的中介也無法從中判斷出密碼來。但是不幸的是惡意的入侵者可以直接傳送雜湊過的密碼假裝是原始的傳送者。
要避免這個問題,Web Services Security Addendum 中加入了另一層額外的保障。與其只是傳送雜湊過的密碼,在附錄中指示了密碼要傳送的摘要版本。這份摘要包含有以密碼組合出的雜湊值,一個 Nonce (用來識別這個要求的唯一字串),以及建立時間。因此沒有兩個密碼的雜湊值會是相同的。一個修正過的 UsernameToken 版本如下:
<!-- Revised UsernameToken --> <wsse:UsernameToken xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" wsu:Id="SecurityToken-58564463-5bdc-4a6b-a7fb-94a0d7357a20"> <wsse:Username>Joe</wsse:Username> <wsse:Password Type="wsse:PasswordDigest"> gpBDXjx79eutcXdtlULIlcrSiRs= </wsse:Password> <wsse:Nonce> h52sI9pKV0BVRPUolQC7Cg== </wsse:Nonce> <wsu:Created>2002-11-04T19:16:50Z</wsu:Created> </wsse:UsernameToken>
雖然每一個合法的要求都應該有不同的雜湊值,但是您必須要留意惡意的使用者可能從某個人合法的要求中拉出完整的 UsernameToken 並安排進他自己不合法的要求當中。您可以藉由指定時間戳記過期的時間 (另一項 WSE 的功能) 為一個充足的小段時間,並強迫伺服器在該時段後視收到的要求為過期來避免。例如,您可能想標示某個訊息在 30 秒之後過期,所以它無法在延滯的情況下傳播,而伺服器在 Created 元素中所指定的 30 秒之後也不會接受 UsernameTokens。但要注意在不同電腦之間時間的同步問題可能會造成合法的要求被拒絕的情況,而在某些情況下又接受已經過期的要求。所以利用建立時間判斷並不是最佳的解決方案。要避開這個風險,Web 服務應該要保留一份來自最近收到的UsernameTokens 的 Nonce 值清單,如果 Nonce 之前有用過就不接受要求。清單中只需要保留尚未過期的臨時項目。如果您在多個要求中收到相同的 Nonce,您就要考慮同時放棄兩份要求,因為有可能是第一份收到的是不合法的要求。同時也請注意到檢查 Nonce 值並不能防止惡意人士阻攔傳入的訊息到達終點,並將原始訊息中的 UsernameToken 用在他自己替換的訊息當中。您必須在訊息中加上數位簽章來防堵這類的攻擊。我們會在本篇文章稍後討論到數位簽章 。
當然不論是哪一種雜湊的方式,都無法避開傳送者和接收者都必須知道使用者的密碼這個事實。在用戶端,我們可以預期使用者會提供自己的密碼。而在伺服器端,就需要某種對照表來查詢合法的使用者及密碼。而 WSE 針對這種能力是透過一個稱做密碼提供者 (Password Provider) 的擴充機制來完成。
密碼提供者
WSE 定義了一個介面─Microsoft.Web.Services.Security.IPasswordProvider─供類別實作,來註冊成為一個密碼提供者。這個介面有一個函式─GetPassword,它是以 Microsoft.Web.Services.Security.UsernameToken 做為輸入。GetPassword 函式會傳回密碼給特定的使用者。這個概念在於您可以使用任何的機制來儲存合法的使用者名稱/密碼組,然後再提供一個實作了 IPasswordProvider 介面的類別讓 WSE 能取用您特殊的密碼儲存機制。您甚至可以選擇在 UsernameToken 的摘要和雜湊值上執行自己私人的組合,以便於控制您自己的驗證基礎結構。
為了通知 WSE 您特有的密碼提供者,您必須要進行一些適當的 WSE 組態設定。這包括到在您應用程式的組態檔的 configruration 元素中加入一個 microsoft.web.services 元素。它也牽涉到指定可以解讀這個特定的組態資訊的 WSE 類別。以下的 configSections 可以加入到 Machine.config 或電腦上個別的 Web.config 檔案中。同樣的,type 屬性必須安排在同一行之中,這裡分行的目的只是為了閱讀方便。這個節點位在您的 .config 檔案的 /configuration/configSections 當中。
<configSections> <section name="microsoft.web.services" type="Microsoft.Web.Services.Configuration.WebServicesConfiguration, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </configSections>
對於 ASP.NET 應用程式而言,以下的項目可以加入做為 Web.config 檔的父組態元素的子元素,以便於通知 WSE 您特有的密碼提供者:
<microsoft.web.services> <security> <!-- This is the class which will provide the password hashes for the UsernameToken signatures. --> <passwordProvider type="WSE_Security.PasswordProvider, WSE-Security" /> </security> </microsoft.web.services>
PasswordProvider 元素的 type 屬性指出了您實作 IPasswordProvider 介面的類別。在這個例子中,我的類別叫做 PasswordProvider,它是定義在 WSE_Security 命名空間中,並且位在 WSE-Security.dll 組件裡。為了簡化起見,我的類別會為每一個使用者傳回一個固定的密碼「opensezme」。在大多數的情況下,這是您安排用來從一個 SQL 資料庫或其他儲存區域取回密碼的程式碼之處。我的簡易類別內容如下:
namespace WSE_Security { public class PasswordProvider : IPasswordProvider { public string GetPassword(UsernameToken token) { return "opensezme"; } } }
產生一個使用 WS-Security 的 WebMethod
現在我們已經可以建立一個 Web 服務並叫用它。我想要建立的 Web 服務會是一個很簡單,像是「Hello World」這個標準範例的 Web 服務,只是我會取用該類別的 SoapContext 對回應進行個人化設定。我的 WebMethod 的程式碼如下。
using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; using System.Web.Services.Protocols; using Microsoft.Web.Services.Security; using Microsoft.Web.Services; namespace WSE_Security { [WebService] public class Hello : System.Web.Services.WebService { [WebMethod] public string PersonalHello() { string response = ""; // Only accept SOAP requests SoapContext requestContext = HttpSoapContext.RequestContext; if (requestContext == null) { throw new ApplicationException("Non-SOAP request."); } // Look through all the tokens in the Tokens collection // for a UsernameToken. foreach (SecurityToken tok in requestContext.Security.Tokens) { if (tok is UsernameToken) { response += "Hello " + ((UsernameToken)tok).Username; } } return response; } } }
我必須在專案中先加入對 Microsoft.Web.Services.dll 的參照,才能利用在我的 WebMethod 當中的 WSE 。接著我會加入兩個 using 子句到程式碼中取用 Microsoft.Web.Services 及 Microsoft.Web.Services.Security。然後我可以建立我的 PersonalHello 這個 WebMethod。
我的 PersonalHello 函式第一件要做的事是取用要求的 SoapContext 物件,方式是透過靜態的 HttpSoapContext.RequestContext 成員。現在我可以取用 WS-Security 標題中所有的資訊。如果沒有內容,我的 Web 方法會傳回一個 Fault,否則的話我會巡覽在要求中送達的所有安全性權杖,檢查是否有 UsernameTokens。如果我看到有 UsernameToken,我會建立一個回應字串,其中包含有一個內含使用者名稱的問候文。
建立使用 WS-Security 的用戶端
為了要瞭解在用戶端如何配合 WSE 來利用 WS-Security,我建立了一個 Windows Form 應用程式,它會在我按下一個按鈕時呼叫我的 Web 服務。如同我在前面 Web 服務類別中所做的,我加入了一個對 Microsoft.Web.Services.dll 的參照,並且包含有在伺服器端程式碼中相同的 using 陳述式。
在用戶端的部份,WSE 提供了 Microsoft.Web.Services.WebServicesClientProtocol 類別,它是繼承自 System.Web.Services.Protocols.SoapHttpClientProtocol 類別。當您在 Visual Studio .NET 中選擇「加入 Web 參考」選項,或是利用 WSDL.exe 公用程式依據 WSDL 建立用戶端程式碼時會使用到 SoapHttpClientProtocol。您可以利用 Visual Studio .NET 中的「加入 Web 參考」選項,或是使用 WSDL.exe 公用程式產生用戶端程式碼的 Proxy 類別,然後修改所產生的 Proxy 類別,把繼承的關係從 SoapHttpClientProtocol 改成 WebServicesClientProtocol。現在 Proxy 類別就會有一個 RequestSoapContext 及 ResponseSoapContext 屬性,您可以用來取用您傳送或接收的 WS-Security 的標題。如果您使用的是「加入 Web 參考」選項,您可以在 Web References 目錄下找到所產生 Proxy 類別的程式碼。對於 C# 的專案而言,您可以在名稱為 WSDL 所在主機的主機名稱的目錄下找到一個叫做 Reference.cs 的檔案。對於 MicrosoftR Visual BasicR .NET 專案,這個檔案會叫做 Reference.vb。我將類別的宣告從
public class Service1 : System.Web.Services.Protocols.SoapHttpClientProtocol {
改成
public class Service1 : Microsoft.Web.Services.WebServicesClientProtocol {
如果您使用 Visual Studio .NET「加入 Web 參考」選項,則在修改其放置所產生程式碼的檔案時,要特別小心。如果您選擇「更新 Web 參考」選項,這時 Visual Studio .NET 會重新產生程式碼,並且覆寫您的變更。
為了要產生要求用的 UsernameToken,我的用戶端程式碼看起來會像下面的樣子。
localhost.Hello proxy = new localhost.Hello(); proxy.Url = endpointInput.Text; UsernameToken TextToken = new UsernameToken(usernameInput.Text, passwordInput.Text, PasswordOption.SendHashed); proxy.RequestSoapContext.Security.Tokens.Add(TextToken); string result; try { result = proxy.PersonalHello(); } catch (Exception ex) { result = ex.Message; } MessageBox.Show(result);
這段程式碼和呼叫一般 Web service 的程式碼之間的差別只在於我建立了 UsernameToken 物件,並將它加入到要求的 Tokens 集合中。UsernameToken 物件的建構函式中需要三個參數:使用者名稱、密碼以及 PasswordOption。在這個例子中我是要傳送雜湊過的密碼。在這段程式中所產生的一個精簡版 SOAP 要求如下所示。您可以注意到要求中有一個 Security 標題,其中有一個 UsernameToken 子元素,包含有使用者名稱、雜湊的密碼和一個可以用來識別這個特定的要求的隨機 Nonce,以及產生的時間。完整的訊息可以參考在本章最後的參考資料一節。
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Header> ... <wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/07/secext"> <wsse:UsernameToken xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" wsu:Id="SecurityToken-58564463-5bdc-4a6b-a7fb-94a0d7357a20"> <wsse:Username>Joe</wsse:Username> <wsse:Password Type="wsse:PasswordDigest"> gpBDXjx79eutcXdtlULIlcrSiRs= </wsse:Password> <wsse:Nonce> h52sI9pKV0BVRPUolQC7Cg== </wsse:Nonce> <wsu:Created>2002-11-04T19:16:50Z</wsu:Created> </wsse:UsernameToken> </wsse:Security> </soap:Header> <soap:Body> <PersonalHello xmlns="http://tempuri.org/" /> </soap:Body> </soap:Envelope>
這段訊息現在可以傳送給 Web 服務,而 WSE SOAP 延伸會驗證要求的一般格式,檢查密碼雜湊和從密碼提供者取得的密碼是否吻合,如果一切正常的話,就要對我們的 Web 方法進行呼叫。我們的 Web 方法會接收到傳入的要求以及填入的 SoapContext,在 Security 標題中找到 UsernameToken,然後依據指定的名稱建立回應字串。傳回的回應是一般的 WebMethod 回應,而我們的用戶端應用程式會顯示傳回的字串。我們已經成功的執行了第一個 WS-Security 應用程式!
在伺服器端驗證 UsernameToken
雖然 WSE 會驗證 Security 標題的語法,並檢查雜湊的密碼和密碼提供者取出的密碼,但是對收到的要求還有一些額外的檢驗工作要進行。例如,在要求中可能有一個以上的 UsernameToken 元素。WS-Security 可以支援要求中安排任何數量的權仗,以因應不同的目的。我們在程式中將一個 UsernameToken 加入到 Tokens 集合的設計就應該是個很明顯的警告─將會有一個以上的權杖能加入到單一的訊息中。事實上,如果您修改用戶端程式碼建立第二個 UsernameToken 並加入到集合中,我們目前的程式碼將會傳回類似以下的結果:
Hello Bob.Hello Alice.
我修改了我們的 Web 方法中的程式碼來驗證 UsernameToken 包含有雜湊的密碼,並且只接受提供單一 UsernameToken 的要求。修改過的程式碼如下:
[WebMethod] public string PersonalHello() { SoapContext requestContext = HttpSoapContext.RequestContext; if (requestContext == null) { throw new ApplicationException("Non-SOAP request."); } // We only allow requests with one security token if (requestContext.Security.Tokens.Count == 1) { foreach (SecurityToken tok in requestContext.Security.Tokens) { // Only accept UsernameTokens if (tok is UsernameToken) { UsernameToken UserToken = (UsernameToken)tok; // Only accept UsernameTokens with a // hashed password. if (UserToken.PasswordOption == PasswordOption.SendHashed) { return "Hello " + UserToken.Username; } else { throw new SoapException( "Invalid UsernameToken password type.", SoapException.ClientFaultCode); } } else { throw new SoapException( "UsernameToken security token required.", SoapException.ClientFaultCode); } } } else { throw new SoapException( "Request must have exactly one security token.", SoapException.ClientFaultCode); } return null; }
我們已經提過惡意的使用者可能會嘗試使用別人的 UsernameToken 元素。這是某種型式的重新執行攻擊,需要額外的程式碼來做防堵。在這裡,我們來看看另一個較安全的選擇。
傳送 X.509 憑證做為 WS-Security 權杖
除了 UsernameTokens 之外,WS-Security 也定義了一個 BinarySecurityToken 元素儲存一些熟知的權杖類型,各自擁有專屬的格式。該規格為 X.509 v3 憑證和 Kerberos v5 票證定義了 BinarySecurityToken 型別。WSE 支援 X.509 憑證,而您可以發現在很多情況下它和 UsernameToken 的處理方式很相近。
憑證存放區
在我們能於要求中加入憑證之前,必須要先決定在那裡可以找到它。在傳統的 MicrosoftR Windows 軟體開發中,憑證是保存在一個叫做憑證存放區 (Certificate Store) 的地方。每一個使用者都有一個私人的憑證存放區,用來進行一些像是數位簽署電子郵件訊息,或是經由 SSL 利用 Web 伺服器進行驗證的工作。憑證本身並不是私有的,因為它們只是一種交出的公開金鑰的數位簽署方式。重要的是對應於憑證中公開金鑰的私密金鑰是位在使用憑證的個人隱密的金鑰存放區當中。這可以讓使用者利用他們的私密金鑰對一個項目進行數位簽章,然後任何一個人可以利用憑證當中的公開金鑰來驗證這個簽名。
.NET Framework 最初的版本並沒有可以用來取用 Windows 憑證存放區的類別,但是有處理 X.509 憑證的類別。不過,WSE 提供有可讓您開啟 Windows 憑證存放區的類別,並從這個容易管理的位置使用憑證。以下的程式碼使用 Microsoft.Web.Services.Security.X509 類別將使用者個人的憑證存放區中所有的憑證名稱填入到一個 listBox 當中。
... using Microsoft.Web.Services; using Microsoft.Web.Services.Security; using Microsoft.Web.Services.Security.X509; ... private X509CertificateStore store; ... private void Form1_Load(object sender, System.EventArgs e) { store = X509CertificateStore.CurrentUserStore( X509CertificateStore.MyStore); store.OpenRead(); foreach(X509Certificate cert in store.Certificates) { listBox1.Items.Add(cert.GetName()); } }
要參考用來從憑證存放區當中選擇 X.509 憑證的完整功能對話方塊的範例,請參閱 WSE 範例程式目錄下的 X509CertificateStoreDialog.cs。
將 X.509 憑證權杖加入到 SOAP 訊息中
現在我們可以找到我們的個人憑證,將它加入到我們的 SOAP 要求中,方式類似之前我們加入 UsernameToken 的作業。以下的程式碼會依據前面我們建立的 listBox 中做出的選擇,從已經開啟的憑證存放區取得憑證,然後加入從憑證所產生的二進位權杖到 Tokens 集合中。
X509Certificate cert = (X509Certificate)store.Certificates[listBox1.SelectedIndex]; proxy.RequestSoapContext.Security.Tokens.Add( new X509SecurityToken(cert));
在伺服器端取得憑證的方式也和存取 UsernameToken 的方式近似。以下這個依據之前 Web 方法中的程式修改過的版本會使用憑證的通用名稱來建立個人化的回應。
[WebMethod] public string PersonalHello() { // Only accept SOAP requests SoapContext requestContext = HttpSoapContext.RequestContext; if (requestContext == null) { throw new ApplicationException("Non-SOAP request."); } // We only allow requests with one security token if (requestContext.Security.Tokens.Count == 1) { foreach (SecurityToken tok in requestContext.Security.Tokens) { // Only accept X.509 Certificates if (tok is X509SecurityToken) { X509SecurityToken certToken = (X509SecurityToken)tok; return "Hello " + certToken.Certificate.GetName(); } else { throw new SoapException( "X.509 security token required.", SoapException.ClientFaultCode); } } } else { throw new SoapException( "Request must have exactly one security token.", SoapException.ClientFaultCode); } return null; }
數位簽章
單純只是在要求中附加 X.509 憑證實際上並不能驗證什麼。憑證是被視為可公開的項目,所以任何人都可以在他們的要求中附上別人的憑證。使用憑證進行驗證的機制主要是植基於之前所提到利用對應於憑證中的公開金鑰的私秘金鑰對某個項目進行簽章。以傳送 SOAP 訊息為例,如果張三利用他的私秘金鑰對 SOAP 的 body 元素產生了一個數位簽章,他可以在要求的標題當中附上和簽章對應的憑證,因此收到訊息的人可以驗證來自張三的要求,並確認訊息在簽章之後不曾有過改變。在要求中附上憑證並不是必要條件,但是對驗證簽章提供了一個便利的方法。
WSE 以非常直接的方式支援數位簽章的建立。和 Tokens 集合一起有一個 SoapContext.Security.Elements 集合,可以讓您加上不同的 WS-Security 元素─包括了 Signature 元素。我們以前一個加入了數位憑證的用戶端程式碼為基礎,現在要使用同樣的憑證簽署要求。進行這項作業的程式碼如下。
X509Certificate cert = (X509Certificate)store.Certificates[listBox1.SelectedIndex]; X509SecurityToken certToken = new X509SecurityToken(cert); proxy.RequestSoapContext.Security.Tokens.Add(certToken); proxy.RequestSoapContext.Security.Elements.Add( new Signature(certToken));
就如同我們之前做過的,我們從之前開啟的存放區取出了憑證,並加入到 Tokens 集合中。然後我們在 Signature 的建構函式中利用 X509SecurityToken 做為參數產生一個新的 Signature 物件,並將這個 Signature 物件加入到 Elements 集合中。一個從這段程式碼所產生簡短的 SOAP 訊息如下所示。完整的 SOAP 訊息則安排在本篇文章最後的參考資料一節。
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Header> <wsrp:path soap:actor="http://schemas.xmlsoap.org/soap/actor/next" soap:mustUnderstand="1" xmlns:wsrp="http://schemas.xmlsoap.org/rp"> <wsrp:action wsu:Id="Id-b856ae70-7a1b-4895-a05c-5f6596ca4429" ... </wsrp:path> ... <wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/07/secext"> <wsse:BinarySecurityToken ValueType="wsse:X509v3" EncodingType="wsse:Base64Binary" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" wsu:Id="SecurityToken-f6f96b4b-23c5-421e-92ff-f1050d531e82"> MIIGkzCCBXugAwIBAgIK . . . 39Vmjd20Lw== </wsse:BinarySecurityToken> <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> <SignedInfo> <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> <Reference URI="#Id-24cc3660-6f1a-41fe-a949-71d7ed9fc636"> <Transforms> <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <DigestValue> /8iL3OP9mfzuixI/ilkhHMbatV0= </DigestValue> </Reference> <Reference URI="#Id-b856ae70-7a1b-4895-a05c-5f6596ca4429"> <Transforms> ... </SignedInfo> <SignatureValue> ZY4MhHzBYz+CBdAz1LhAFjy6QxQoKJoA7l2eG45QV0hDIJrmXwLEGrPnpX+uPan5+MS6hm+oL /sGTbKJ/DJMp/t5ZyqY1qvngGQLcYXRy538zemwFfeGN5R2wmOoUSeCBUqprQVUbnkz+qlVp/ 5f7t7VGW2Ee55Q3ol+ApVoFQE= </SignatureValue> <KeyInfo> <wsse:SecurityTokenReference> <wsse:Reference URI="#SecurityToken-f6f96b4b-23c5-421e-92ff-f1050d531e82" /> </wsse:SecurityTokenReference> </KeyInfo> </Signature> </wsse:Security> </soap:Header> <soap:Body wsu:Id="Id-24cc3660-6f1a-41fe-a949-71d7ed9fc636" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> <PersonalHello xmlns="http://tempuri.org/" /> </soap:Body> </soap:Envelope>
如果想充份瞭解在這個要求中不同標題元素的作用,請參考參考部份其他的規格,像是 WS-Routing 以及 Web Services Security Addendum。針對我們的討論,我們會以 WSE 如何使用 WS-Security 來簽署我們的要求為主。
在這段訊息中首先要注意到的是在 Security 標題中有一個 BinarySecurityToken 元素。這個元素的屬性指出它是一個 X.509 v3 憑證,是以 Base 64 的方式編碼。這是我們在最後兩個範例中加入到 Tokens 集合的 X509SecurityToken。再一次提醒,它並不一定要被包含在訊息中,但是它讓驗證簽章的作業較為便利。
接下來要注意到的是在 Security 標題中現在有了一個 Signature 元素。在 Signature 中有三個子元素:SignedInfo 元素、SignatureValue 元素、以及 KeyInfo 元素。SignedInfo 元素定義了在要求中實際被簽章的資料、它如何被正式化,以及使用哪一種演算法來計算簽章。其中重要的項目是 reference 元素的清單,它定義了訊息中哪些資料是真正經過簽署。為了簡潔起見,我拿掉了一些 reference 元素,但是您可以注意到留下來的兩個,每一個都有對應的 URI 屬性。這些 URI 屬性對應到經過簽署的元素的 ID 屬性。例如,第一個參照指出了一個內容為「#Id-24cc3660-」的 URI。如果您搜尋訊息,會看到這個 URI 對應到我們訊息中 Body 元素中的 ID。因此 Body 就是這個訊息中經過簽署過的資訊之一。第二個參照則對應到 Path 標題中的 action 元素,這是 WS-Routing 規格的一部份。WSE 會自動附上 Path 標題和 Timestamp 標題,而在標題中很多額外的資訊會被包含在數位簽章中。以下的列表是 WSE 在 SignedInfo 標題中會參照到的元素,都會包含在數位簽章的範圍中。
/soap:Envelope/soap:Header/wsrp:path/wsrp:action /soap:Envelope/soap:Header/wsrp:path/wsrp:to /soap:Envelope/soap:Header/wsrp:path/wsrp:from /soap:Envelope/soap:Header/wsrp:path/wsrp:id /soap:Envelope/soap:Header/wsu:Timestamp/wsu:Created /soap:Envelope/soap:Header/wsu:Timestamp/wsu:Expires /soap:Envelope/soap:Body
之所以在 path 和 Timestamp 標題中只有部份子標題經過簽署,而不是簽署整個 path 及 Timestamp 元素,是因為在 WS-Routing 的環境中中介者有可能在 path 及 Timestamp 標題中加入其他元素,做為 SOAP 層級轉送時附帶的傳播訊息。因此,一個數位簽章很可能因為這些標題被修改而失去作用。
有關這個要求中的 Signature 元素最後一件要注意的事是 KeyInfo 參照回包含有我們的憑證的 BinarySecurityToken。它指出了用來簽署這個要求的金鑰來源。當然,對於憑證而言是指對應到用來產生數位簽章的憑證中公開金鑰的私秘金鑰。該公開金鑰可以用來驗證資料是否來自握有對應私秘金鑰的人員。
驗證數位簽章
WSE SOAP 延伸會驗證數位簽章的語法,但光是知道在訊息中存在合法的簽章並不能讓我們判斷這個訊息是否來自特定的人士。WS-Security 及XML 數位簽章規格─(Extensible Markup Language) XML-Signature Syntax and Processing─對於將簽章包含在 XML 中的做法十分有彈性。例如,您可以期望某個訊息來自王五,您可能看到訊息中有來自王五的簽名,但是您只能確定在路徑標題的傳送資訊中的簽名是可信的。這並不保證您可能最關心的部份 (通常是訊息的本文) 是來自王五,除非您自行做一些驗證。
以下的程式碼會檢查要求中是否有 Signature 元素存在,透過 SignatureOptions 屬性來驗證訊息的本文是有簽章,因為我們很清楚知道是誰送出訊息,而且接收到的內容和傳出時一樣,我們就會建立一個依據簽署該要求的憑證內容產生一個個人的問候。
[WebMethod] public string PersonalHello() { // Only accept SOAP requests SoapContext requestContext = HttpSoapContext.RequestContext; if (requestContext == null) { throw new ApplicationException("Non-SOAP request."); } // Look for Signatures in the Elements collection foreach (Object elem in requestContext.Security.Elements) { if (elem is Signature) { Signature sign = (Signature)elem; // Verify that signature signs the body of the request if (sign != null && (sign.SignatureOptions & SignatureOptions.IncludeSoapBody) != 0) { // Determine what kind of token is used // with the signature. if (sign.SecurityToken is UsernameToken) { return "Hello " + ((UsernameToken)sign.SecurityToken).Username; } else if (sign.SecurityToken is X509SecurityToken) { return "Hello " + ((X509SecurityToken)sign.SecurityToken) .Certificate.GetName(); } } } } // No approriate signature found throw new SoapException("No valid signature found", SoapException.ClientFaultCode); }
對部份訊息簽章
在前面的例子中,我們對 SOAP 訊息產生了一個 WSE 預設的簽章。這個簽章涵蓋了 SOAP 本章和 SOAP 標題當中不同的部份,這對大多數的情況是適用的。但是有些情況下您可能不希望是對訊息預設的部份或甚至整份 SOAP 本文進行簽署。例如,您可能會允許有中間人對 SOAP 的本文部份內容進行修改,結果讓簽名成為不合法。因此我們必須要能控制究竟哪些部份是需要簽署的。
您有兩種方式可判斷訊息中哪些部份要涵蓋在數位簽章適用的範圍內。在前面的例子中,我們利用 Signature 類別的 SignatureOptions 屬性來判斷簽署的範圍是否涵蓋 SOAP 本文。您也可以利用 SignatureOptions 屬性來指示 SOAP 訊息中哪些部份要被涵蓋在簽署的範圍內。您可以利用以下任何定義在 SignatureOptions 列舉型別內的各個旗標的組合:
IncludePath IncludePathAction IncludePathFrom IncludePathId IncludePathTo IncludeSoapBody IncludeTimestamp IncludeTimestampCreated IncludeTimestampExpires
還有一個可以在 SignatureOptions 中指定的值是 IncludeNone。這代表以上任何一個項目都不需要被放在簽章範圍中。所以若是您打算指定 IncludeNone 這個 SignatureOption,那又何必要對訊息進行數位簽章的作業?答案是要利用其他的機制來判斷究竟要傳送訊息的哪些部份。
WS-Security 提供您兩個選擇來判斷什麼要簽署,什麼不用。第一個選擇是指定訊息的轉換型式然後依據結果產生簽章。不過 WSE 並不支援這個選項。另一個方式是對您要簽章的元素指示一個 Id 屬性,然後在 Signature 元素的 SignedInfo 部份加入對這個元素的參照。這也是 WSE 產生對 SignatureOptions 列舉型別中不同部份的簽章的方式。但是 WSE 也允許您自行產生 Id 屬性,然後手動加入對簽章的 SignedInfo 部份的參照。這讓您可以簽署自訂的 SOAP 標題以及任何您想要在本文中簽署的部份。您只要確定有指定 Id 屬性,而在參照部份有加入對它的 href 項目。我們接下來要看一個只簽署部份 SOAP 本文的例子。
ASP.NET Web services 利用 .NET Framework 中的 XmlSerializer 來產生並剖析所接收及傳送的 SOAP 訊息。您可以控制許多的細節,藉由指定屬性來標示您的內容,以及控制 XmlSerializer 的作業方式來決定要如何進行序列化 (Serialization) 的作業。要對部份的 XML 內容加入一個 Id 屬性並不難,只要指定它的值,並將這個值加入到簽章部份的 references 元素就行了。
為了說明如何進行這個工作,我建立了一個 ASP.NET WebMethod 會傳回一個包含有訂單的複雜型別。我只想要對 XML 中實際訂單元素的部份進行簽署,好讓用戶端可以確定這是來自我的訊息。首先我們產生了這個會從我的 WebMethod 傳回的複雜類別。在這個類別中,包含有實際訂單編號,但是同時也有 ID 屬性被視做元素屬性。這會是我們在參照區中用來對照的 ID。在這個屬性中指定正確的命名空間,與在 WS-Security 規格中的命名空間相吻合相當重要,所以我們利用 XmlAttribute 屬性的 Namespace 參數將它設定為
public class PONum { [XmlAttribute("Id", Namespace="http://schemas.xmlsoap.org/ws/2002/07/utility")] public string ID; [XmlText] public string PONumber; }
我們的 WebMethod 必須要做好些事。首先它需要產生一個 GUID 用來做為唯一的 ID。它必須要產生上述類別的執行個體,並指定 ID 屬性的值,它以正確的格式包含有這個 GUID 的值。它也會指定實際訂單編號,在我們這個例子中,始終都是送出相同的單號。接下來,它會產生一個以用來簽署訂單的 X.509 憑證為基礎的安全性權杖。
要在 ASP.NET 中使用憑證,它必須要位在 ASP.NET 作業處理程序可以取用的憑證存放區當中。同樣的對應到憑證公開金鑰的私秘金鑰也必須位在 ASP.NET 可以取用的金鑰存放區當中。在我的例子中,我使用了一個位在電腦憑證存放區當中的憑證。為了要讓事情能順利進行,我將電腦實際金鑰存放區的存取權限開放給「ASPNET 使用者」(ASP.NET 工作處理序執行的依據帳號)。這潛在的也開放了存取權限給其他應用程式取用電腦的金鑰存放區,所以您必須要修改這裡的作法。請參考 WSE 文件中標題為「Managing X.509 Certificates」的章節以瞭解有關儲存及取用您的電腦上的憑證及金鑰的資訊。
在我們有了安全性權杖之後,我們可以產生一個 Signature 的執行個體,將我們的權杖傳給建構函式。我們立即設定 SignatureOptions 屬性值為SignatureOptions.IncludeNone,所以它不會包含訊息中任何預設會含入的部份。現在我們呼叫 Signature 物件的 AddReference 方法,指示我們指定給 ID 屬性相同的字串,只是我們在前面多加了一個「#」符號,來指出它是一個本機參照。這個 Web 方法的程式碼如下所示:
[WebMethod] public PONum GetPONumber() { SoapContext responseContext = HttpSoapContext.ResponseContext; PONum PO = new PONum(); Guid referenceID = Guid.NewGuid(); PO.ID = "Id:" + referenceID.ToString(); PO.PONumber = "PO10025"; X509CertificateStore store = X509CertificateStore.LocalMachineStore( X509CertificateStore.MyStore); store.OpenRead(); X509Certificate cert = store.FindCertificateBySubjectName( "CN = mattpo.redmond.corp.microsoft.com")[0]; X509SecurityToken token = new X509SecurityToken(cert); responseContext.Security.Tokens.Add(token); Signature sig = new Signature(token); sig.SignatureOptions = SignatureOptions.IncludeNone; sig.AddReference(new Reference("#" + PO.ID)); responseContext.Security.Elements.Add(sig); return PO; }
用戶端要做的最後一步是檢查傳回的訂單適當的簽章。我們的動作是驗證所傳回的 ID 位在該簽章的參照當中,而且所使用的憑證是我們所期望的對象。至於呼叫 Web 服務的程式碼和驗證憑證的程式碼如下所示。
localhost.Service1Wse proxy = new localhost.Service1Wse(); localhost.PONum po = proxy.GetPONumber(); foreach (Object element in proxy.ResponseSoapContext.Security.Elements) { if (element is Signature) { Signature sig = (Signature)element; foreach (Reference reference in sig.SignedInfo.References) { if (reference.Uri == "#" + po.Id) { X509Certificate signatureCert = ((X509SecurityToken) sig.SecurityToken).Certificate; if (signatureCert.Equals(poCert)) MessageBox.Show("It's signed!"); } } } }
結論
本篇文章說明了 Web Services Enhancements for Microsoft .NET 如何提供您善用 WS-Security 規格的能力,並介紹了如何使用 WSE 進行 UsernameToken 驗證和使用 X.509 驗證來對 SOAP 訊息進行數位簽署。WSE 讓您能檢視並驗證可以直接安排在 SOAP 訊息當中對 WS-Security 的支援。還有一些其他 WSE 所提供的 WS-Security 能力並沒有在這裡介紹到,像是使用不同形式的加密作業。我鼓勵您安裝 WSE,寫一些程式碼,並且檢閱所產生的訊息,以及在「參考資料」一節中提供的訊息,來充份感受 WS-Security 如何加強您的 Web 服務的功能。
參考資料
含有雜湊過的 UsernameToken 的 SOAP 訊息
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Header> <wsrp:path soap:actor="http://schemas.xmlsoap.org/soap/actor/next" soap:mustUnderstand="1" xmlns:wsrp="http://schemas.xmlsoap.org/rp"> <wsrp:action> http://tempuri.org/PersonalHello </wsrp:action> <wsrp:to> http://localhost:8080/wsdk-security/verifiedhello.asmx </wsrp:to> <wsrp:id> uuid:14f61c50-586c-42ec-8286-c5c9fa8bfce1 </wsrp:id> </wsrp:path> <wsu:Timestamp xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> <wsu:Created>2002-11-04T19:16:50Z</wsu:Created> <wsu:Expires>2002-11-04T19:21:50Z</wsu:Expires> </wsu:Timestamp> <wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/07/secext"> <wsse:UsernameToken xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" wsu:Id="SecurityToken-58564463-5bdc-4a6b-a7fb-94a0d7357a20"> <wsse:Username>Joe</wsse:Username> <wsse:Password Type="wsse:PasswordDigest"> gpBDXjx79eutcXdtlULIlcrSiRs= </wsse:Password> <wsse:Nonce> h52sI9pKV0BVRPUolQC7Cg== </wsse:Nonce> <wsu:Created>2002-11-04T19:16:50Z</wsu:Created> </wsse:UsernameToken> </wsse:Security> </soap:Header> <soap:Body> <PersonalHello xmlns="http://tempuri.org/" /> </soap:Body> </soap:Envelope>
含有以 X.509 憑證數位簽章過的 SOAP 訊息
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Header> <wsrp:path soap:actor="http://schemas.xmlsoap.org/soap/actor/next" soap:mustUnderstand="1" xmlns:wsrp="http://schemas.xmlsoap.org/rp"> <wsrp:action wsu:Id="Id-b856ae70-7a1b-4895-a05c-5f6596ca4429" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> http://tempuri.org/PersonalHello </wsrp:action> <wsrp:to wsu:Id="Id-e3fa8752-df7d-4a16-a883-f98800bf24f7" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> http://localhost:8080/wsdk-security/signedhello.asmx </wsrp:to> <wsrp:id wsu:Id="Id-64bdd986-1d54-4176-bbc3-c38255fcfedf" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> uuid:15810d11-91d9-44cb-b3c8-016ff9d93b78 </wsrp:id> </wsrp:path> <wsu:Timestamp xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> <wsu:Created wsu:Id="Id-134651fa-1791-4679-abf7-cbbc100fbeb9"> 2002-11-05T23:35:59Z </wsu:Created> <wsu:Expires wsu:Id="Id-9c106440-3955-4f62-903d-c30c6c9e8d27"> 2002-11-05T23:40:59Z </wsu:Expires> </wsu:Timestamp> <wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/07/secext"> <wsse:BinarySecurityToken ValueType="wsse:X509v3" EncodingType="wsse:Base64Binary" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" wsu:Id="SecurityToken-f6f96b4b-23c5-421e-92ff-f1050d531e82"> MIIGkzCCBXugAwIBAgIK . . . 39Vmjd20Lw== </wsse:BinarySecurityToken> <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> <SignedInfo> <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> <Reference URI="#Id-24cc3660-6f1a-41fe-a949-71d7ed9fc636"> <Transforms> <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <DigestValue> /8iL3OP9mfzuixI/ilkhHMbatV0= </DigestValue> </Reference> <Reference URI="#Id-b856ae70-7a1b-4895-a05c-5f6596ca4429"> <Transforms> <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <DigestValue> rH3m6W9zglaAMMzP7sD9yvwzEdA= </DigestValue> </Reference> <Reference URI="#Id-e3fa8752-df7d-4a16-a883-f98800bf24f7"> <Transforms> <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <DigestValue> tzgO0kc7HSomAxAokgHw3/hkL+E= </DigestValue> </Reference> <Reference URI="#Id-64bdd986-1d54-4176-bbc3-c38255fcfedf"> <Transforms> <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <DigestValue> Ls05OCaMfqqaIFj2vV8a/aHQQBo= </DigestValue> </Reference> <Reference URI="#Id-134651fa-1791-4679-abf7-cbbc100fbeb9"> <Transforms> <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <DigestValue> VqOIop+9EeJXGIwB3TqAqXgiKUI= </DigestValue> </Reference> <Reference URI="#Id-9c106440-3955-4f62-903d-c30c6c9e8d27"> <Transforms> <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <DigestValue> 3d9WMNwuLIrRfSyaWCsl63d+wDA= </DigestValue> </Reference> </SignedInfo> <SignatureValue> ZY4MhHzBYz+CBdAz1LhAFjy6QxQoKJoA7l2eG45QV0hDIJrmXwLEGrPnpX+
uPan5+MS6hm+oL/sGTbKJ/DJMp/t5ZyqY1qvngGQLcYXRy538zemwFfeGN5
R2wmOoUSeCBUqprQVUbnkz+qlVp/5f7t7VGW2Ee55Q3ol+ApVoFQE= </SignatureValue> <KeyInfo> <wsse:SecurityTokenReference> <wsse:Reference URI="#SecurityToken-f6f96b4b-23c5-421e-92ff-f1050d531e82" /> </wsse:SecurityTokenReference> </KeyInfo> </Signature> </wsse:Security> </soap:Header> <soap:Body wsu:Id="Id-24cc3660-6f1a-41fe-a949-71d7ed9fc636" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> <PersonalHello xmlns="http://tempuri.org/" /> </soap:Body> </soap:Envelope>
含有以 X.509 憑證對訊息部份內容進行數位簽署的 SOAP 訊息
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Header> <wsu:Timestamp xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> <wsu:Created>2002-11-07T21:51:51Z</wsu:Created> <wsu:Expires>2002-11-07T21:56:51Z</wsu:Expires> </wsu:Timestamp> <wsse:Security soap:mustUnderstand="1" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/07/secext"> <wsse:BinarySecurityToken ValueType="wsse:X509v3" EncodingType="wsse:Base64Binary" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility" wsu:Id="SecurityToken-547309ee-532f-40ce-a370-a64be85e977e"> MIIHRjC ... HVUjaoy </wsse:BinarySecurityToken> <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> <SignedInfo> <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> <Reference URI="#Id:ce249a29-aa9a-427a-b0c4-830cdc7f481a"> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <DigestValue>7yhtDGpxNtkFGT9+1vWHI7sQL1c=</DigestValue> </Reference> </SignedInfo> <SignatureValue> tNj18ILnxAyc/3AoNCRb+ZBcYcIp5KCKTFLCTNhAzuokk5m1S8FOBvFYTUdy1qCU2i655/
KCcIzZ7lzLSqY57iaoWgdpQBAWvEEhxkSNuGGl/qoknNhc4B2SN24t1AniB4UwNFvo2u6
rHiBr3nSfAv0rSPuGa32c3Ri8LRcqZ5M= </SignatureValue> <KeyInfo> <wsse:SecurityTokenReference> <wsse:Reference URI= "#SecurityToken-547309ee-532f-40ce-a370-a64be85e977e" /> </wsse:SecurityTokenReference> </KeyInfo> </Signature> </wsse:Security> </soap:Header> <soap:Body> <GetPONumberResponse xmlns="http://tempuri.org/"> <GetPONumberResult d4p1:Id="Id:ce249a29-aa9a-427a-b0c4-830cdc7f481a" xmlns:d4p1="http://schemas.xmlsoap.org/ws/2002/07/utility"> PO10025 </GetPONumberResult> </GetPONumberResponse> </soap:Body> </soap:Envelope>
posted on 2008-06-25 11:56 Above The Sky 阅读(701) 评论(0) 编辑 收藏 举报