先前的筆記「WCF BasicHttpBinding 加密傳輸與身分驗證」裡面提到如何在 SOAP header 裡面加入Security 元素(亦即 Username 和 Password)。那篇的解法繞了遠路,且只能適用 HTTPS 安全傳輸協定,而這次使用的可同時適用 HTTP 和 HTTPS。欲呼叫的目標 web service 也跟上次一樣,不是 WCF service,而是以 Java 寫成的 web service。
這裡的「同時適用 HTTP 和 HTTPS」指的是欲呼叫的目標 web service 的 URL 可以是 http:// 開頭,也可以是 https:// 開頭。上回的解法雖然是採用 basicHttpBinding,但由於在設定 WCF 組態時,使用了如下設定:
以致於無論目標 web service 的 URL 是以 http:// 還是 https:// 開頭,實際上都會強制走加密安全傳輸協定。這是因為在設定 basicHttpBinding 時,若 security mode 設定為 "Transport" 或 "TransportWithCredential",都會強制使用加密傳輸。
這對先前的解法有何影響?原本在測試環境時,目標 web service 是 https:// 開頭的網址,這沒問題。然而等到運行於正式環境時,網址變成 http://(而非 https://),先前那篇筆記的方法就會出問題了。試過幾種組態,執行時要不是出現「The provided URI scheme 'http' is invalid; expected 'https'」錯誤,就是傳送的 SOAP 訊息標頭裡面沒有加上 Username 和 Password(security mode = "TransportCredentialOnly" 或 "Message")。這試誤過程花了我不少時間,實驗各種參數選項組合來觀察執行結果,也體會了 WCF 的彈性。(不知為何突然很感恩神農嚐百草的辛勞 XD)
在進入正題前,再說一下相關環境和限制:
欲實現的效果
目標 web service 要求每一次存取其服務時,SOAP 訊息的 header 裡面都要包含 Username 和 Password 資訊,以確保只有知道特定帳戶密碼的用戶端能夠存取其服務。像這樣:
如同往常,試誤過程中,我除了使用 WCF tracing 功能(很簡單,只要貼幾行 XML 字串到應用程式組態檔就行),也還用了 Fiddler 來觀察 SOAP 訊息。當 Fiddler 攔截到的用戶端發出之 SOAP 訊息標頭裡面沒有包含 Security 元素(如下圖),我就知道這次又失敗了。
如果像下圖這樣,就知道這次的方法可行:
解法
無論目標 web service 的 endpoint 是以 http:// 開頭還是 https:// 開頭,以下設定都適用:
原來這麼簡單呀!關鍵就在於多快能發現關鍵問題其實是「如何在每次的 WCF 呼叫中加入自訂的標頭參數」。說穿了不值錢,可不是? ^^
如果覺得大喇喇把 Username 和 Password 寫在組態檔中不大妥當,或者 SOAP 標頭中的某些參數值是動態的、必須在執行時期視情況而定的,則可以用程式碼的方式來動態加入標頭參數。實際作法可參考底下的延伸閱讀清單。
延伸閱讀
這裡的「同時適用 HTTP 和 HTTPS」指的是欲呼叫的目標 web service 的 URL 可以是 http:// 開頭,也可以是 https:// 開頭。上回的解法雖然是採用 basicHttpBinding,但由於在設定 WCF 組態時,使用了如下設定:
<basicHttpBinding name="myHttpBinding" ....> <security mode="TransportWithCredential"> <transport clientCredentialType="None" /> <message clientCredentialType="UserName" /> </security> </basicHttpBinding>
以致於無論目標 web service 的 URL 是以 http:// 還是 https:// 開頭,實際上都會強制走加密安全傳輸協定。這是因為在設定 basicHttpBinding 時,若 security mode 設定為 "Transport" 或 "TransportWithCredential",都會強制使用加密傳輸。
這對先前的解法有何影響?原本在測試環境時,目標 web service 是 https:// 開頭的網址,這沒問題。然而等到運行於正式環境時,網址變成 http://(而非 https://),先前那篇筆記的方法就會出問題了。試過幾種組態,執行時要不是出現「The provided URI scheme 'http' is invalid; expected 'https'」錯誤,就是傳送的 SOAP 訊息標頭裡面沒有加上 Username 和 Password(security mode = "TransportCredentialOnly" 或 "Message")。這試誤過程花了我不少時間,實驗各種參數選項組合來觀察執行結果,也體會了 WCF 的彈性。(不知為何突然很感恩神農嚐百草的辛勞 XD)
MSDN 上面有整理 Common Security Scenarios,列出一些常見的情境和用法。如果你也碰到 security 方面的問題,不妨先去那裏找找看有沒有符合你碰到的狀況。很幸運,裡面正好沒有我要的 Orz
在進入正題前,再說一下相關環境和限制:
- 欲呼叫的服務不是 WCF,而是以 Java 寫成的 web service。對方採用 SOAP 1.1 規格,這表示我的 WCF 用戶端要使用 basicHttpBinding。
- 這次的目標 web service 不支援 https://,僅提供 http:// 開頭的 URL。
- 開發環境為 Visual Studio 2012 + .NET Framework 4.5。
欲實現的效果
目標 web service 要求每一次存取其服務時,SOAP 訊息的 header 裡面都要包含 Username 和 Password 資訊,以確保只有知道特定帳戶密碼的用戶端能夠存取其服務。像這樣:
<soapenv:Header xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:Security> <wsse:UsernameToken Id="http://xmethods.net/xspace"> <wsse:Username>Michael</wsse:Username> <wsse:Password>guesswhat</wsse:Password> </wsse:UsernameToken> </wsse:Security> <wsa:To>https://target.service.company.com:123/qoo/QooServices</wsa:To> <wsa:Action>getFoo</wsa:Action> <wsa:MessageID>uuid:9a6DA6FF-011C-4000-E000-5E3A0A244C97</wsa:MessageID> </soapenv:Header>
如同往常,試誤過程中,我除了使用 WCF tracing 功能(很簡單,只要貼幾行 XML 字串到應用程式組態檔就行),也還用了 Fiddler 來觀察 SOAP 訊息。當 Fiddler 攔截到的用戶端發出之 SOAP 訊息標頭裡面沒有包含 Security 元素(如下圖),我就知道這次又失敗了。
如果像下圖這樣,就知道這次的方法可行:
解法
無論目標 web service 的 endpoint 是以 http:// 開頭還是 https:// 開頭,以下設定都適用:
<client> <endpoint name="FooServices" address="http://foo.web/services" binding="basicHttpBinding" bindingConfiguration="fooBinding" contract="Foo.Services"> <headers> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:UsernameToken> <wsse:Username>TheUserName</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">GuessWhat</wsse:Password> </wsse:UsernameToken> </wsse:Security> </headers> </endpoint> </client>
原來這麼簡單呀!關鍵就在於多快能發現關鍵問題其實是「如何在每次的 WCF 呼叫中加入自訂的標頭參數」。說穿了不值錢,可不是? ^^
如果覺得大喇喇把 Username 和 Password 寫在組態檔中不大妥當,或者 SOAP 標頭中的某些參數值是動態的、必須在執行時期視情況而定的,則可以用程式碼的方式來動態加入標頭參數。實際作法可參考底下的延伸閱讀清單。
延伸閱讀
- Stackoverflow:How to add a custom header to every WCF calls?
- Adding Custom Message Headers to a WCF Service using Inspectors & Behaviors
- How to add WCF client Message Header without using Operation Context
沒有留言: