閱讀146 返回首頁    go 阿裏雲 go 技術社區[雲棲]


[WCF安全係列]談談WCF的客戶端認證[用戶名/密碼認證]

對於基於Internet的應用,基於用戶名和密碼的認證方式是最為常用的,而WCF為你提供了不同模式的用戶名認證方式。首先還是從用戶憑證的表示說起。

基於用戶名/密碼的用戶憑證通過類型UserNamePasswordClientCredential表示。而在ClientCredentials中,隻讀屬性UserName表示這樣一個用戶憑證。你可以按照Windows憑證的方式為ChannelFactory<TChannel>或者ClientBase<TChannel>基於用戶名/密碼憑證。

   1: public class ClientCredentials
   2: {
   3:      //其他成員
   4:      public UserNamePasswordClientCredential UserName { get; }
   5: } 
   6: public sealed class UserNamePasswordClientCredential
   7: {
   8:     //其他成員
   9:     public string Password {get; set; }
  10:     public string UserName { get; set; }
  11: }

用戶名/密碼憑證在客戶端的設置很容易,但是我們關心的是服務端采用怎樣的機製來驗證這個憑證。WCF為你提供了如下三種方式來驗證憑證中用戶名是否和密碼相符:

  • Windows:將用戶名和密碼映射為Windows帳號和密碼,采用Windows認證;
  • MembershipProvider:利用配置的MembershipProvider驗證用戶名和密碼;
  • 自定義:通過繼承抽象類UsernamePasswordValidator,自定義用戶名/密碼驗證器進行驗證。

WCF通過枚舉UserNamePasswordValidationMode定了上述三種用戶名/密碼認證模式。該枚舉定義如下,其中Windows是默認選項。

   1: public enum UserNamePasswordValidationMode
   2: {
   3:     Windows,
   4:     MembershipProvider,
   5:     Custom
   6: }

上述三種認證模式的設置最終通過之前提到過的ServiceCredentials這一服務行為進行設置的。從下麵的定義我們可以看出,ServiceCredentials定義了隻讀屬性UserNameAuthentication用於基於用戶名/密碼認證的相關設置。屬性的類型為UserNamePasswordServiceCredential,定義其中的UserNamePasswordValidationMode屬性表示采用的認證模式。如果選擇了需要通過屬性MembershipProvider設置采用的MembershipProvider。如果選擇了Custom,則需要通過CustomUserNamePasswordValidator屬性指定你自定義的UserNamePasswordValidator對象。

   1: public class ServiceCredentials: SecurityCredentialsManager, IServiceBehavior
   2: {
   3:     //其他成員
   4:      public UserNamePasswordServiceCredential UserNameAuthentication { get; }
   5: }
   6: public sealed class UserNamePasswordServiceCredential
   7: {
   8:     //其他成員
   9:     public UserNamePasswordValidator CustomUserNamePasswordValidator { get; set; }
  10:     public MembershipProvider MembershipProvider { get; set; }
  11:     public UserNamePasswordValidationMode UserNamePasswordValidationMode { get; set; }
  12: }

接下來我們通過實例演示的方式來如何通過MembershipProvider進行基於用戶名/密碼認證,而對於自定義UserNamePasswordValidator的實例我會在介紹安全會話的時候進行演示。

Membership是ASP.NET中一個重要的模塊,旨在進行基於用戶名/密碼的認證和對應的帳號管理。Membership采用策略設計模式,所有的API通過幾個靜態Membership類暴露出來,而相應的功能實現在具體的Membership提供者中。所有的提供者繼承自同一個抽象類MembershipProvider。ASP.NET提供了兩種類型的提供者:SqlMembershipProviderActiveDirectoryMembershipProvider。前者將用戶存儲於SQL Server數據庫中,而後者則直接建立在AD之上,本實例采用SqlMembershipProvider,在前麵一個實例演示中,我們創建了以計算服務為場景的解決方案,現在我們直接沿用它。

我們首要的任務是在用於存儲帳戶信息的SQL Server數據庫,為此你可以先在本地SQL Server創建一個空的數據庫(假設起名為AspNetDb)。你接著需要在該數據庫中創建SqlMembershipProvider所需的數據表和相應的存儲過程。這些數據庫對象的創建,需要借助這個工具。你隻需要以命令行的方式執行如下aspnet_regsql.exe(無需任何參數),相應的向導就會出現。

在向導彈出的前兩個窗體中保持默認設置,直接點擊“下一步”後,會出現一個數據庫選擇窗體。此時你需要選擇我們剛剛創建的數據庫,點擊“確認”後,相關的數據庫對象會為你創建出來。

這些創建出來的數據表可以同時服務於多個應用,所有每一個表中都具有一個名稱為ApplicationId的字段來明確該條記錄對應的應用。而所有應用記錄維護在aspnet_Applications這麼一個表中。現在我們需要通過執行下麵一段SQL腳本在該表中添加一條表示我們應用的記錄。我們應用起名為MembershipAuthenticationDemo。

   1: INSERT INTO [aspnet_Applications]
   2:            ([ApplicationName]
   3:            ,[LoweredApplicationName]
   4:            ,[ApplicationId]
   5:            ,[Description])
   6: VALUES
   7:            (
   8:              'MembershipAuthenticationDemo'
   9:              ,'membershipauthenticationdemo'
  10:              ,NEWID()
  11:              ,''
  12:         )

現在數據庫方麵已經準備就緒,我們接著來完成編程和配置方麵的工作。我們不打算從新創建一個解決方案,而是直接對之前演示的實例進行改造。我們采用自我寄宿的方式,由於Membership隸屬於ASP.NET,所以我們需要添加System.Web.dll的引用,如果你采用的是.NET Frameowrk 4.0(本例所示的配置也是基於該版本),你還需額外添加對的引用。接下來,我們需要在服務寄宿方麵所做的工作就是將下麵一段配置整個拷貝到app.config中。

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <connectionStrings>
   4:     <add name="AspNetDb" connectionString="Server=.; Database=AspNetDb; Uid=sa; Pwd=password"/>
   5:   </connectionStrings>
   6:   <system.web>
   7:     <membership defaultProvider="myProvider">
   8:       <providers>
   9:         <add name="myProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" 
  10:              connectionStringName="AspNetDb" applicationName="MembershipAuthenticationDemo" requiresQuestionAndAnswer="false"/>
  11:       </providers>
  12:     </membership>
  13:   </system.web>
  14: <system.serviceModel>
  15:   <bindings>
  16:     <ws2007HttpBinding>
  17:       <binding name="userNameCredentialBinding">
  18:         <security mode="Message">
  19:           <message clientCredentialType="UserName"/>
  20:         </security>
  21:       </binding>
  22:     </ws2007HttpBinding>
  23:   </bindings>
  24:   <services>
  25:     <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="membershipAuthentication">
  26:       <endpoint address="https://127.0.0.1/calculatorservice" binding="ws2007HttpBinding" bindingConfiguration="userNameCredentialBinding" contract="Artech.WcfServices.Contracts.ICalculator"/>
  27:     </service>
  28:   </services>
  29:   <behaviors>
  30:     <serviceBehaviors>
  31:       <behavior  name="membershipAuthentication">
  32:         <serviceCredentials>
  33:           <serviceCertificate storeLocation="LocalMachine" storeName ="My" x509FindType="FindBySubjectName" findValue="Jinnan-PC"/>
  34:           <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="myProvider"/>
  35:         </serviceCredentials>
  36:       </behavior>
  37:     </serviceBehaviors>
  38:   </behaviors>
  39:   </system.serviceModel>
  40: </configuration>

考慮到有些人可能對ASP.NET下的Membership相關配置不太了解,在這裏我對上述這段配置進行以下簡單的說明。

  • 配置名稱為的連接字符串連接的是我們剛剛創建的數據庫,並通過aspnet_regsql.exe工具在該數據庫中創建了所需的數據庫對象;
  • 表示Membership配置節的<system.web>/<membership>節點下配置了唯一的SqlMembershipProvider,配置名稱為。上麵配置的連接字符創名稱AspNetDb配置在connectionStringName屬性中,意味著該SqlMembershipProvider會將我們創建的數據庫作為用戶帳號存儲;
  • 服務終結點采用WS2007HttpBinding,采用Message安全模式,客戶端憑證類型被設置為;
  • 服務應用了一個配置名稱為membershipAuthentication的服務行為,該行為中通過<serviceCertificate>節點設置了服務證書。在表示用戶名/密碼認證配置的<userNameAuthentication>節點中,將認證模式設置成MembershipProvider,而membershipProviderName屬性的值為我們在<system.web>/<membership>中設置的MembershipProvider的名稱。

到目前為止,在我們創建的數據庫中並沒有用戶帳戶記錄。為了演示認證的效果,我們必須創建相關用戶帳戶記錄。為了省事,我直接將相關的代碼寫在了服務寄宿的代碼中。如下麵的代碼片斷所示,在對服務進行寄宿之前,我通過調用Membership的靜態方法CreateUser創建了一個用戶名、密碼和Email分別為Zhansan、Pass@word和zhanshan@gmail.com的帳號。

   1: if (Membership.FindUsersByName("Zhansan").Count == 0)
   2: {
   3:     Membership.CreateUser("Zhansan", "Pass@word", "zhanshan@gmail.com");
   4: }
   5: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
   6: {
   7:     host.Open();
   8:     Console.Read();
   9: }

接下來我們需要對客戶端的配置進行相應的調整,整個配置內容如下麵的XML片斷所示。對於這段配置有一點需要注意的是:終結點應用了一個名稱為peerTrustSvcCertValidation的行為,該行為中將服務證書認證模式設置成PeerTrust,所以你需要。

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     <bindings>
   5:       <ws2007HttpBinding>
   6:         <binding name="userNameCredentialBinding">
   7:           <security mode="Message">
   8:             <message clientCredentialType="UserName"/>
   9:           </security>
  10:         </binding>
  11:       </ws2007HttpBinding>
  12:     </bindings>
  13:     <client>
  14:       <endpoint name="calculatorService" behaviorConfiguration="peerTrustSvcCertValidation"  address="https://127.0.0.1/calculatorservice" binding="ws2007HttpBinding" bindingConfiguration="userNameCredentialBinding" contract="Artech.WcfServices.Contracts.ICalculator">
  15:         <identity>
  16:           <certificateReference storeLocation="LocalMachine" storeName ="My" x509FindType="FindBySubjectName" findValue="Jinnan-PC"/>
  17:         </identity>
  18:       </endpoint>
  19:     </client>
  20:     <behaviors>
  21:       <endpointBehaviors>
  22:         <behavior name="peerTrustSvcCertValidation">
  23:           <clientCredentials>
  24:             <serviceCertificate>
  25:               <authentication certificateValidationMode="PeerTrust"/>
  26:             </serviceCertificate>
  27:           </clientCredentials>
  28:         </behavior>
  29:       </endpointBehaviors>
  30:     </behaviors>
  31:   </system.serviceModel>
  32: </configuration>

最後,我麼來編寫如下一段客戶端進行服務調用的程序。在下麵的代碼中,我進行了兩次服務調用。但是創建服務代理對象的ChannelFactory<ICalculator>被設置了不同的用戶名憑證。其中第一個是正確的用戶名和密碼,後一個卻指定了一個根本不存在的用戶名。

   1: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService"))
   2: {
   3:     UserNamePasswordClientCredential credential = channelFactory.Credentials.UserName;
   4:     credential.UserName     = "Zhansan";
   5:     credential.Password     = "Pass@word";
   6:     ICalculator calculator  = channelFactory.CreateChannel();
   7:     calculator.Add(1, 2);
   8:     Console.WriteLine("服務調用成功...");
   9: }
  10: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService"))
  11: {
  12:     UserNamePasswordClientCredential credential = channelFactory.Credentials.UserName;
  13:     credential.UserName     = "lisi";
  14:     credential.Password     = "Pass@word";
  15:     ICalculator calculator  = channelFactory.CreateChannel();
  16:     try
  17:     {
  18:         calculator.Add(1, 2);
  19:     }
  20:     catch
  21:     {
  22:         Console.WriteLine("服務調用失敗...");
  23:     }
  24: }

輸出結果:

   1: 服務調用成功...
   2: 服務調用失敗...

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 16:34:33

  上一篇:go  [WCF安全係列]談談WCF的客戶端認證[Windows認證]
  下一篇:go  [WCF安全係列]談談WCF的客戶端認證[X.509證書認證]