WCF basicHttpBinding with Custom Header

先前的筆記「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 組態時,使用了如下設定:

<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 標頭中的某些參數值是動態的、必須在執行時期視情況而定的,則可以用程式碼的方式來動態加入標頭參數。實際作法可參考底下的延伸閱讀清單。

延伸閱讀


沒有留言:

技術提供:Blogger.
回頂端⬆️