使用者認證的WCF服務—basicHttpBinding繫結
1. 建立 basicHttpUserAuth.webhost 專案
basicHttpBinding 應該是 WCF 中最常被使用的繫結模式﹐這一篇將介紹 basicHttpBinding 如何使用自訂使用者認證的WCF服務。
繼續延續之前的文章範例﹐建立一個WCF 服務的網站專案 basicHttpUserAuth.webhost ﹐將自動產生的IService.cs﹑Service.cs及Service.svc三個檔案移除﹐然後參考使用者認證的WCF服務—範例服務程式庫(WCF Library)這一篇中的 MyProducts.lib.dll 及 UserAuthorization.lib.dll。
接著加入一個文字檔﹐將檔名改為 MyProducts.svc ﹐內容只寫一行
<%@ ServiceHost Language="C#" Debug="false" Service="MyProducts.lib.ProductService"%>
完成上述的動作後﹐再來就是修改組態檔。先將組態檔做到以下的部分﹐在 security 的 Mode 設定成 message 看看會有什麼情況。
<system.serviceModel>
<services>
<service behaviorConfiguration="Products.Behavior" name="MyProducts.lib.ProductService">
<endpoint binding="basicHttpBinding" bindingConfiguration="Products.BasicHttpBinding"
contract="MyProducts.lib.IProductService" />
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="Products.BasicHttpBinding">
<security mode="Message" />
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="Products.Behavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
將上述組態存檔後以瀏覽器執行MyProducts.svc﹐出現了如下的錯誤。一般使用basicHttpBinding是搭配Transport(傳輸層)的加密方式﹐如果一定要使用Message則必須改為混搭風格使用TransportWithMessageCredential。
現在將security的mode改為Transport﹐再將組態檔做一些修改﹐以下特別標示出的地方是新加入的設定。
Security之下的transport的clientCredentialType屬性必須為None。而在behavior中也必須加入憑證的設定。
然後再開啟瀏覽器執行一次MyProducts.svc。
這是因為basicHttpBinding使用Transport的安全性必須使用 https﹐所以這裏要變更IIS的設定﹐請參考第 2 小節的 IIS SSL設定。
假設IIS設定已完成﹐再開啟瀏覽器執行﹐這時看到的錯誤已不同﹐因為我們必須變更使用https才對。
網址改為 https://localhost:853/basicHttpUserAuth.webhost/MyProducts.svc 因為不是用標準的443 port﹐所以網址上必須將port number寫出。
再次執行後﹐畫面出現了警告﹐暫時先不管這個警告﹐按下畫面上的[繼續瀏覽此網站(不建議)]﹐可以看到畫面像是我們所要的。
那麼可以得到WSDL了嗎?如果點下了 https://localhost:853/basicHttpUserAuth.webhost/MyProducts.svc?wsdl 會發現不能如想像般顯示出WSDL的定義資料。這裏需要再修改WCF的組態檔。在behavior的serviceMetadata中需要再加入一個 httpsGetEnabled=true 的屬性值。
<behavior name="Products.Behavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<serviceCertificate findValue="MyWCFCert"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
</serviceCredentials>
</behavior>
完成設定後﹐再一次執行﹐就可以得到想要的畫面了。
2. IIS SSL 設定
因為basicHttpBinding使用傳輸層加密(Transport)必須使用https﹐因此需要設定SSL。開啟IIS之後在站台使用滑鼠右鍵﹐選擇「編輯繫結」。
在站台繫結畫面上按下[新增]鍵﹐將類型選擇 https﹐連接埠預設為443﹐我故意另設別的port 853﹐接著在SSL憑證之處選擇所要使用的憑證。確定後就多了一個繫結設定。
上述設定完成後﹐回到IIS 畫面﹐接著點選我們的應用程式basicHttpUserAuth.webhost﹐在右側點選[SSL設定]。
將[需要SSL]的核取方塊勾選﹐然後套用﹐IIS的設定到此結束。
3. basicWinClientForUserValidator 測試程式
建立一個新的WinForm程式專案﹐basicWinClientForUserValidator﹐參考上述的basicHttpBinding的WCF服務https://localhost:853/basicHttpUserAuth.webhost/MyProducts.svc?wsdl 。
程式的功能和寫法都和之前的4.4.2小節的程式差不多﹐因此直接看app.config和程式內容。
App.config
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IProductService">
<security mode="Transport">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://localhost:853/basicHttpUserAuth.webhost/MyProducts.svc"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IProductService"
contract="ServiceHost.IProductService"
name="BasicHttpBinding_IProductService" />
</client>
</system.serviceModel>
Form1.cs
1: namespace basicWinClientForUserValidator {
2: public partial class Form1 : Form {
3: public Form1() {
4: InitializeComponent();
5: }
6:
7: private void btnSaySomething_Click(object sender, EventArgs e) {
8: txtException.Text = "";
9: txtOutput.Text = "";
10:
11: ServiceHost.ProductServiceClient proxy = null;
12: try{
13: proxy = new basicWinClientForUserValidator.ServiceHost.ProductServiceClient();
14: proxy.ClientCredentials.UserName.UserName = txtUid.Text;
15: proxy.ClientCredentials.UserName.Password = txtPwd.Text;
16: proxy.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
17:
18: txtOutput.Text = proxy.SaySomething(txtInput.Text);
19: }catch(Exception er){
20: txtException.Text = er.InnerException.Message;
21: //txtException.Text = er.Message;
22: }
23: }
24: }
25: }
將程式執行之後﹐可惜不符合預期﹐出現了錯誤。
出現了”基礎連接已關閉: 無法為 SSL/TLS 安全通道建立信任關係。”錯誤訊息。這是因為Client端無法驗證Server的SSL憑證﹐因此請求失敗。現在使用的憑證是自我簽署的憑證並不安全﹐所以會檢查不通過﹐就如同IE執行WSDL服務網址跳出的此網站的安全性憑證有問題一樣。
可是在IE上還可以按下「繼續瀏覽此網站」﹐那是不是我們在程式上也可以忽略呢?答案是可以的﹐現在新增一個Util.cs的class。
Util.cs
1: namespace basicWinClientForUserValidator {
2: public static class Util {
3: public static void SetCertificatePolicy() {
4: ServicePointManager.ServerCertificateValidationCallback += RemoteCertificateValidate;
5: }
6:
7: private static bool RemoteCertificateValidate(object sender,
8: X509Certificate cert, X509Chain chain, SslPolicyErrors error) {
9: //System.Console.WriteLine("Warning, trust any certificate.");
10: return true;
11: }
12: }
13: }
接著在Client程式一開始先呼叫Util.SetCertificatePolicy()就可以了。
1: private void btnSaySomething_Click(object sender, EventArgs e) {
2: txtException.Text = "";
3: txtOutput.Text = "";
4:
5: Util.SetCertificatePolicy();
6:
7: ServiceHost.ProductServiceClient proxy = null;
8: try{
9: proxy = new basicWinClientForUserValidator.ServiceHost.ProductServiceClient();
10: proxy.ClientCredentials.UserName.UserName = txtUid.Text;
11: proxy.ClientCredentials.UserName.Password = txtPwd.Text;
12: proxy.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
13:
14: txtOutput.Text = proxy.SaySomething(txtInput.Text);
15: }catch(Exception er){
16: txtException.Text = er.InnerException.Message;
17: //txtException.Text = er.Message;
18: }
19: }
把程式重新編譯後再執行﹐已經可以讓執行正確的執行了。
4. [此網站的安全性憑證有問題]﹐開發時期如何解決?
上一小節中因為憑證的安全性問題﹐所以多加了一個class去忽略憑證的檢查﹐但是如果憑證是可通過驗證的那一段是可以不需要的。可是開發期間通常就是用自我簽署的憑證﹐那麼該怎麼辦?其實只要自我簽署的憑證主體是本機的主機名稱﹐就可以通過檢查。
假設我的Server主機名稱是KEVIN-H﹐在建立憑證時其主體設為”CN=KEVIN-H”即可。
既然憑證換了﹐WCF的設定當然也要修改﹐所以WCF的組態檔web.config中的憑證要修改。
不過﹐這裏有另一個問題﹐如上圖﹐Server上憑證中有兩個主體名稱是相同的。這時如果使用FindPrivateKey工具要去找這個憑證檔﹐會得到在store中這個主體有多個相同名稱的錯誤。
這時必須用憑證指紋方式﹐憑證指紋可以在憑證管理中將憑證打開﹐在詳細資料中就可以找到憑證指紋﹐將一長串16位元的字串複製出來使用。注意這一串16位元的字串最開頭有一個空白字元必須去除﹐將這串直接複製到記事本時﹐最前面的空白字元會看不到﹐但其實是存在的。
將FrindPrivateKey指令改為如下即可
憑證檔找到後再利用calcls.exe賦予讀取憑證檔讀取的權限後﹐這時開啟IE執行WSDL https://localhost:853/basicHttpUserAuth.webhost/MyProducts.svc ﹐出現錯誤了
同樣是因為憑證主體名稱造成的﹐這時要修改web.config的設定﹐把對憑證搜尋的條件改為比較精準的方式﹐這裏同樣改為憑證指紋。
關於WCF自訂使用者認證的介紹到此﹐至於Java如何調用請參考另外一個文件﹐後續有可能的話再研究Android如何調用。