WCF的用戶名+密碼認證方式

概述

今天在做Master Data Service(後面簡稱MDS)項目時需要通過WCF來使用MDS的API,從而對MDS的數據進行操作。在這個過程中,遇到了一個棘手的問題,就是在客戶端調用Web Service時的身份認證問題,於是乎對WCF的認證方式做了一個簡單的瞭解。在這裏還要感謝蔡總陪我加班一起解決問題,在蔡總的大力支持下問題得以解決。我們解決問題的方式採用了客戶端用戶名+密碼的方式來進行身份認證,這只是諸多WCF認證方式當中的一種。

用戶名+密碼認證的三種模式

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

public class ClientCredentials
{
     //其他成員
     public UserNamePasswordClientCredential UserName { get; }
} 
public sealed class UserNamePasswordClientCredential
{
    //其他成員
    public string Password {get; set; }
    public string UserName { get; set; }
}

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

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

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

public enum UserNamePasswordValidationMode
{
    Windows,
    MembershipProvider,
    Custom
}

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

public class ServiceCredentials: SecurityCredentialsManager, IServiceBehavior
{
    //其他成員
     public UserNamePasswordServiceCredential UserNameAuthentication { get; }
}
public sealed class UserNamePasswordServiceCredential
{
    //其他成員
    public UserNamePasswordValidator CustomUserNamePasswordValidator { get; set; }
    public MembershipProvider MembershipProvider { get; set; }
    public UserNamePasswordValidationMode UserNamePasswordValidationMode { get; set;

}

通過MembershipProvider進行用戶名+密碼的認證

Membership是ASP.NET中一個重要的模塊,旨在進行基於用戶名/密碼的認證和對應的帳號管理。Membership採用策略設計模式,所有的API通過幾個靜態Membership類暴露出來,而相應的功能實現在具體的Membership提供者中。所有的提供者繼承自同一個抽象類MembershipProvider。ASP.NET提供了兩種類型的提供者:SqlMembershipProvider和ActiveDirectoryMembershipProvider。前者將用戶存儲於SQL Server數據庫中,而後者則直接建立在AD之上,本實例採用SqlMembershipProvider,在前面一個實例演示中,我們創建了以計算服務爲場景的解決方案,現在我們直接沿用它。
首要的任務是在用於存儲帳戶信息的SQL Server數據庫,爲此可以先在本地SQL Server創建一個空的數據庫(假設起名爲AspNetDb)。接着需要在該數據庫中創建SqlMembershipProvider所需的數據表和相應的存儲過程。這些數據庫對象的創建,需要藉助aspnet_regsql.exe這個工具。你只需要以命令行的方式執行如下aspnet_regsql.exe(無需任何參數),相應的嚮導就會出現。
在嚮導彈出的前兩個窗體中保持默認設置,直接點擊“下一步”後,會出現一個數據庫選擇窗體。此時你需要選擇我們剛剛創建的數據庫,點擊“確認”後,相關的數據庫對象會爲你創建出來。
這些創建出來的數據表可以同時服務於多個應用,所有每一個表中都具有一個名稱爲ApplicationId的字段來明確該條記錄對應的應用。而所有應用記錄維護在aspnet_Applications這麼一個表中。現在需要通過執行下面一段SQL腳本在該表中添加一條表示應用的記錄。將其命名爲MembershipAuthenticationDemo。

INSERT INTO [aspnet_Applications]
           ([ApplicationName]
           ,[LoweredApplicationName]
           ,[ApplicationId]
           ,[Description])
VALUES
           (
             'MembershipAuthenticationDemo'
             ,'membershipauthenticationdemo'
             ,NEWID()
             ,''
           )

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

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="AspNetDb" connectionString="Server=.; Database=AspNetDb; Uid=sa; Pwd=password"/>
  </connectionStrings>
  <system.web>
    <membership defaultProvider="myProvider">
      <providers>
        <add name="myProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="AspNetDb" applicationName="MembershipAuthenticationDemo"
requiresQuestionAndAnswer="false"/>
      </providers>
    </membership>
  </system.web>
  <system.serviceModel>
    <bindings>
      <ws2007HttpBinding>
        <binding name="userNameCredentialBinding">
          <security mode="Message">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </ws2007HttpBinding>
    </bindings>
    <services>
      <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="membershipAuthentication">
        <endpoint address="http://127.0.0.1/calculatorservice" binding="ws2007HttpBinding"
bindingConfiguration="userNameCredentialBinding" contract="Artech.WcfServices.Contracts.ICalculator"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior  name="membershipAuthentication">
          <serviceCredentials>
            <serviceCertificate storeLocation="LocalMachine" storeName ="My" x509FindType="FindBySubjectName" findValue="YueXu-PC"/>
            <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="myProvider"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

至此,在我們創建的數據庫中並沒有用戶帳戶記錄。爲了演示認證效果,我們需要創建相關用戶帳戶記錄。爲了方便,我直接將相關的代碼寫在了服務寄宿的代碼中。如下面的代碼片斷所示,在對服務進行寄宿之前,我通過調用Membership的靜態方法CreateUser創建了一個用戶名、密碼和Email分別爲xuyue、password01和xuyue1000@hotmail的帳號。

if (Membership.FindUsersByName("xuyue").Count == 0)
{
    Membership.CreateUser("xuyue", "password01", "[email protected]");
}
using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
{
    host.Open();
    Console.Read();
}

接下來我們需要對客戶端的配置進行相應的調整,整個配置內容如下面的XML片斷所示。對於這段配置有一點需要注意的是:終結點應用了一個名稱爲peerTrustSvcCertValidation的行爲,該行爲中將服務證書認證模式設置成PeerTrust,所以你需要通過MMC證書管理單元的導出/導入功能將YueXu-PC證書導入到“受信任人(Trusted People)”存儲區。

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <ws2007HttpBinding>
        <binding name="userNameCredentialBinding">
          <security mode="Message">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </ws2007HttpBinding>
    </bindings>
    <client>
      <endpoint name="calculatorService" behaviorConfiguration="peerTrustSvcCertValidation"
address=http://127.0.0.1/calculatorservice binding="ws2007HttpBinding"
bindingConfiguration="userNameCredentialBinding" contract="Artech.WcfServices.Contracts.ICalculator">
        <identity>
          <certificateReference storeLocation="LocalMachine" storeName ="My" x509FindType="FindBySubjectName" findValue="YueXu-PC"/>
        </identity>
      </endpoint>
    </client>
    <behaviors>
      <endpointBehaviors>
        <behavior name="peerTrustSvcCertValidation">
          <clientCredentials>
            <serviceCertificate>
              <authentication certificateValidationMode="PeerTrust"/>
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

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

using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService"))
{
    UserNamePasswordClientCredential credential = channelFactory.Credentials.UserName;
    credential.UserName     = "xuyue";
    credential.Password     = "password01";
    ICalculator calculator  = channelFactory.CreateChannel();
    calculator.Add(1, 2);
    Console.WriteLine("服務調用成功...");
}
using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService"))
{
    UserNamePasswordClientCredential credential = channelFactory.Credentials.UserName;
    credential.UserName     = "wrongName";
    credential.Password     = "wrongPWD";
    ICalculator calculator  = channelFactory.CreateChannel();
    try
    {
        calculator.Add(1, 2);
    }
    catch
    {
        Console.WriteLine("服務調用失敗...");
    }
}

輸出結果:

1: 服務調用成功...

2: 服務調用失敗...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章