C# 實現身份驗證之WCF篇(1)


WCF身份驗證一般常見的方式有:自定義用戶名及密碼驗證、X509證書驗證、ASP.NET成員資格(membership)驗證、SOAP Header驗證、Windows集成驗證、WCF身份驗證服務(AuthenticationService),這些驗證方式其實網上都有相關的介紹文章,我這里算是一個總結吧,順便對於一些注意細節進行說明,以便大家能更好的掌握這些知識。

第一種:自定義用戶名及密碼驗證(需要借助X509證書)

由於該驗證需要借助於X509證書,所以我們需要先創建一個證書,可以利用MS自帶的makecert.exe程序來制作測試用證書,使用步驟:請依次打開開始->Microsoft Visual Studio 2010(VS菜單,版本不同,名稱有所不同)->Visual Studio Tools->Visual Studio 命令提示,然后執行以下命令:

makecert -r -pe -n "CN=ZwjCert" -ss TrustedPeople -sr LocalMachine -sky exchange

上述命令中除了我標粗的部份可改成你實際的請求外(為證書名稱),其余的均可以保持不變,命令的意思是:創建一個名為ZwjCert的證書將將其加入到本地計算機的受信任人區域中。

如果需要查看該證書,那么可以通過MMC控制台查詢證書,具體操作步驟如下:

運行->MMC,第一次打開Windows沒有給我們准備好直接的管理證書的入口,需要自行添加,添加方法如下:

1. 在控制台菜單,文件→添加/刪除管理單元→添加按鈕→選”證書”→添加→選”我的用戶賬戶”→關閉→確定
2. 在控制台菜單,文件→添加/刪除管理單元→添加按鈕→選”證書”→添加→選”計算機賬戶”→關閉→確定

這樣MMC中左邊就有菜單了,然后依次展開:證書(本地計算機)->受信任人->證書,最后就可以在右邊的證書列表中看到自己的證書了,如下圖示:

 

證書創建好,我們就可以開始編碼了,本文主要講的就是WCF,所以我們首先定義一個WCF服務契約及服務實現類(后面的各種驗證均采用該WCF服務),我這里直接采用默認的代碼,如下:

namespace WcfAuthentications
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        string GetData(int value);
 
        [OperationContract]
        CompositeType GetDataUsingDataContract(CompositeType composite);
 
    }
 
    [DataContract]
    public class CompositeType
    {
        bool boolValue = true;
        string stringValue = "Hello ";
 
        [DataMember]
        public bool BoolValue
        {
            get { return boolValue; }
            set { boolValue = value; }
        }
 
        [DataMember]
        public string StringValue
        {
            get { return stringValue; }
            set { stringValue = value; }
        }
    }
}
 
 
namespace WcfAuthentications
{
    public class Service1 : IService1
    {
        public string GetData(int value)
        {
            return string.Format("You entered: {0}", value);
        }
 
        public CompositeType GetDataUsingDataContract(CompositeType composite)
        {
            if (composite == null)
            {
                throw new ArgumentNullException("composite");
            }
            if (composite.BoolValue)
            {
                composite.StringValue += "Suffix";
            }
            return composite;
        }
    }
}

要實現用戶名及密碼驗證,就需要定義一個繼承自UserNamePasswordValidator的用戶名及密碼驗證器類CustomUserNameValidator,代碼如下:

namespace WcfAuthentications
{
    public class CustomUserNameValidator : UserNamePasswordValidator
    {
 
        public override void Validate(string userName, string password)
        {
            if (null == userName || null == password)
            {
                throw new ArgumentNullException();
            }
            if (userName != "admin" && password != "wcf.admin") //這里可依實際情況下實現用戶名及密碼判斷
            {
                throw new System.IdentityModel.Tokens.SecurityTokenException("Unknown Username or Password");
            }
 
        }
    }
}

代碼很簡單,只是重寫其Validate方法,下面就是將創建WCF宿主,我這里采用控制台程序

代碼部份:

namespace WcfHost
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var host = new ServiceHost(typeof(Service1)))
            {
                host.Opened += delegate
                {
                    Console.WriteLine("Service1 Host已開啟!");
                };
                host.Open();
                Console.ReadKey();
            }
        }
    }
}

APP.CONFIG部份(這是重點,可以使用WCF配置工具來進行可視化操作配置,參見:http://www.cnblogs.com/Moosdau/archive/2011/04/17/2019002.html):

<system.serviceModel>
  <bindings>
    <wsHttpBinding>
      <binding name="Service1Binding">
        <security mode="Message">
          <message clientCredentialType="UserName" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <services>
    <service behaviorConfiguration="Service1Behavior" name="WcfAuthentications.Service1">
      <endpoint address="" binding="wsHttpBinding" bindingConfiguration="Service1Binding"
        contract="WcfAuthentications.IService1">
        <identity>
          <dns value="ZwjCert" />
        </identity>
      </endpoint>
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:8732/WcfAuthentications/Service1/" />
        </baseAddresses>
      </host>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="Service1Behavior">
        <serviceMetadata httpGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="false" />
        <serviceCredentials>
          <serviceCertificate findValue="ZwjCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="TrustedPeople" />
          <userNameAuthentication userNamePasswordValidationMode="Custom"
            customUserNamePasswordValidatorType="WcfAuthentications.CustomUserNameValidator,WcfAuthentications" />
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

這里面有幾個需要注意的點:

1.<dns value="ZwjCert" />與<serviceCertificate findValue="ZwjCert" ..>中的value必需都為證書的名稱,即:ZwjCert;

2.Binding節點中需配置security節點,message子節點中的clientCredentialType必需設為:UserName;

3.serviceBehavior節點中,需配置serviceCredentials子節點,其中serviceCertificate 中各屬性均需與證書相匹配,userNameAuthentication的userNamePasswordValidationMode必需為Custom,customUserNamePasswordValidatorType為上面自定義的用戶名及密碼驗證器類的類型及其程序集

最后就是在客戶端使用了,先引用服務,然后看下App.Config,並進行適當的修改,如下:

<system.serviceModel>
    <bindings>
        <wsHttpBinding>
            <binding name="WSHttpBinding_IService1" >
                <security mode="Message">
                    <transport clientCredentialType="Windows" proxyCredentialType="None"
                        realm="" />
                    <message clientCredentialType="UserName" negotiateServiceCredential="true"
                        algorithmSuite="Default" />
                </security>
            </binding>
        </wsHttpBinding>
    </bindings>
    <client>
        <endpoint address="http://localhost:8732/WcfAuthentications/Service1/"
            binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1"
            contract="ServiceReference1.IService1" name="WSHttpBinding_IService1">
            <identity>
                <dns value="ZwjCert" />
            </identity>
        </endpoint>
    </client>
</system.serviceModel>

為了突出重點,我這里對Binding節點進行了精簡,去掉了許多的屬性配置,僅保留重要的部份,如:security節點,修改其endpoint下面的identity中<dns value="ZwjCert" />,這里的value與服務中所說的相同節點相同,就是證書名稱,如果不相同,那么就會報錯,具體的錯誤消息大家可以自行試下,我這里限於篇幅內容就不貼出來了。

客戶端使用服務代碼如下:

namespace WCFClient
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var proxy = new ServiceReference1.Service1Client())
            {
                proxy.ClientCredentials.UserName.UserName = "admin";
                proxy.ClientCredentials.UserName.Password = "wcf.admin";
                string result = proxy.GetData(1);
                Console.WriteLine(result);
                var compositeObj = proxy.GetDataUsingDataContract(new CompositeType() { BoolValue = true, StringValue = "test" });
                Console.WriteLine(SerializerToJson(compositeObj));
            }
            Console.ReadKey();
        }
 
        /// <summary>
        /// 序列化成JSON字符串
        /// </summary>
        static string SerializerToJson<T>(T obj) where T:class
        {
            var serializer = new DataContractJsonSerializer(typeof(T));
            var stream = new MemoryStream();
            serializer.WriteObject(stream,obj);
 
            byte[] dataBytes = new byte[stream.Length];
 
            stream.Position = 0;
            stream.Read(dataBytes, 0, (int)stream.Length);
            string dataString = Encoding.UTF8.GetString(dataBytes);
            return dataString;
        }
    }
}

運行結果如下圖示:

  

如果不傳入用戶名及密碼或傳入不正確的用戶名及密碼,均會報錯:

第二種:X509證書驗證


首先創建一個證書,我這里就用上面創建的一個證書:ZwjCert;由於服務器端及客戶端均需要用到該證書,所以需要導出證書,在客戶端的電腦上導入該證書,以便WCF可進行驗證。

WCF服務契約及服務實現類與第一種方法相同,不再重貼代碼。

WCF服務器配置如下:

<system.serviceModel>
  <bindings>
    <wsHttpBinding>
      <binding name="Service1Binding">
        <security mode="Message">
          <message clientCredentialType="Certificate" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <services>
    <service behaviorConfiguration="Service1Behavior" name="WcfAuthentications.Service1">
      <endpoint address="" binding="wsHttpBinding" bindingConfiguration="Service1Binding"
        contract="WcfAuthentications.IService1">
      </endpoint>
      <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      <host>
        <baseAddresses>
          <add baseAddress="http://127.0.0.1:8732/WcfAuthentications/Service1/" />
        </baseAddresses>
      </host>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="Service1Behavior">
        <serviceMetadata httpGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="false" />
        <serviceCredentials>
          <serviceCertificate findValue="ZwjCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="TrustedPeople" />
          <clientCertificate>
            <authentication certificateValidationMode="None"/>
          </clientCertificate>
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

這里需注意如下幾點:

1.<message clientCredentialType="Certificate" />clientCredentialType設為:Certificate;

2.需配置serviceCredentials節點,其中serviceCertificate 中各屬性均需與證書相匹配,clientCertificate里面我將authentication.certificateValidationMode="None",不設置采用默認值其實也可以;

客戶端引用服務,自動生成如下配置信息:

<system.serviceModel>
    <bindings>
        <wsHttpBinding>
            <binding name="WSHttpBinding_IService1">
                <security mode="Message">
                    <transport clientCredentialType="Windows" proxyCredentialType="None"
                        realm="" />
                    <message clientCredentialType="Certificate" negotiateServiceCredential="true"
                        algorithmSuite="Default" />
                </security>
            </binding>
        </wsHttpBinding>
    </bindings>
    <client>
        <endpoint address="http://127.0.0.1:8732/WcfAuthentications/Service1/"
            binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1"
            contract="ServiceReference1.IService1" name="WSHttpBinding_IService1" behaviorConfiguration="Service1Nehavior">
            <identity>
                <certificate encodedValue="AwAAAAEAAAAUAAAAkk2avjNCItzUlS2+Xj66ZA2HBZYgAAAAAQAAAOwBAAAwggHoMIIBVaADAgECAhAIAOzFvLxLuUhHJRwHUUh9MAkGBSsOAwIdBQAwEjEQMA4GA1UEAxMHWndqQ2VydDAeFw0xNTEyMDUwMjUyMTRaFw0zOTEyMzEyMzU5NTlaMBIxEDAOBgNVBAMTB1p3akNlcnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALfGfsiYpIVKu3gPJl790L13+CZWt6doePZHmcjMl+xPQKIR2fDvsCq9ZxzapDgiG4T3mgcVKUv55DBiuHcpXDvXt28m49AjdKwp924bOGKPM56eweKDCzYfLxy5SxaZfA9qjUhnPq3kGu1lfWjXbsp1rKI1UhKJg5b2j0V7AOC3AgMBAAGjRzBFMEMGA1UdAQQ8MDqAEH/MEXV8FHNLtxvllQ5SMbihFDASMRAwDgYDVQQDEwdad2pDZXJ0ghAIAOzFvLxLuUhHJRwHUUh9MAkGBSsOAwIdBQADgYEAdBtBNTK/Aj3woH2ts6FIU3nh7FB2tKQ9L3k6QVL+kCR9mHuqWtYFJTBKxzESN2t0If6muiktcO+C8iNwYpJpPzLAOMFMrTQhkO82gcdr9brQzMWPTraK1IS+GGH8QBIOTLx9zfV/iCIXxRub+Sq9dmRSQjKDeLeHWoE5I6FkQJg=" />
            </identity>
        </endpoint>
    </client>
  <behaviors>
    <endpointBehaviors>
      <behavior name="Service1Nehavior">
        <clientCredentials>
          <clientCertificate findValue="ZwjCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="TrustedPeople" />
        </clientCredentials>
      </behavior>
    </endpointBehaviors>
  </behaviors>
</system.serviceModel>

可以看出endpoint節點下的identity.certificate的encodedValue包含了加密的數據,另外需要手動增加clientCertificate配置信息,該信息表示證書在本地電腦存放的位置,當然也可以通過代碼來動態指定,如:proxy.ClientCredentials.ClientCertificate.SetCertificate("ZwjCert", StoreLocation.LocalMachine, StoreName.My);

客戶端使用服務代碼如下:

static void Main(string[] args)
{
    using (var proxy = new ServiceReference1.Service1Client())
    {
 
        //proxy.ClientCredentials.ClientCertificate.SetCertificate("ZwjCert", StoreLocation.LocalMachine, StoreName.My); //直接動態指定證書存儲位置
        string result = proxy.GetData(1);
        Console.WriteLine(result);
        var compositeObj = proxy.GetDataUsingDataContract(new CompositeType() { BoolValue = true, StringValue = "test" });
        Console.WriteLine(SerializerToJson(compositeObj));
    }
    Console.ReadKey();
}

網上還有另類的針對X509證書驗證,主要是采用了自定義的證書驗證器類,有興趣的可以參見這篇文章:http://www.cnblogs.com/ejiyuan/archive/2010/05/31/1748363.html

第三種:ASP.NET成員資格(membership)驗證

 由於該驗證需要借助於X509證書,所以仍然需要創建一個證書(方法如第一種中創建證書方法相同):ZwjCert;

由於該種驗證方法是基於ASP.NET的membership,所以需要創建相應的數據庫及創建賬號,創建數據庫,請通過運行aspnet_regsql.exe向導來創建數據庫及其相關的表,通過打開ASP.NET 網站管理工具(是一個自帶的管理網站),並在上面創建角色及用戶,用於后續的驗證;

這里特別說明一下,若采用VS2013,VS上是沒有自帶的GUI按鈕來啟動該管理工具網站,需要通過如下命令來動態編譯該網站:

cd C:\Program Files\IIS Express
iisexpress.exe /path:C:\Windows\Microsoft.NET\Framework\v4.0.30319\ASP.NETWebAdminFiles /vpath:/WebAdmin /port:12345 /clr:4.0 /ntlm

編譯時若出現報錯:“System.Configuration.StringUtil”不可訪問,因為它受保護級別限制,請將WebAdminPage.cs中代碼作如下修改:

//取消部份:
string appId = StringUtil.GetNonRandomizedHashCode(String.Concat(appPath, appPhysPath)).ToString("x", CultureInfo.InvariantCulture);
 
//新增加部份:
Assembly sysConfig = Assembly.LoadFile(@"C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Configuration.dll");
Type sysConfigType = sysConfig.GetType("System.Configuration.StringUtil");
string appId = ((int)sysConfigType.GetMethod("GetNonRandomizedHashCode").Invoke(null, new object[] { String.Concat(appPath, appPhysPath), true })).ToString("x", CultureInfo.InvariantCulture);

這樣就可以按照命令生生成的網址進行訪問就可以了。如果像我一樣,操作系統為:WINDOWS 10,那么不好意思,生成的網站雖然能夠打開,但仍會報錯:

遇到錯誤。請返回上一頁並重試。

目前沒有找到解決方案,網上有說ASP.NET網站管理工具在WIN10下不被支持,到底為何暫時無解,若大家有知道的還請分享一下(CSDN有別人的求問貼:http://bbs.csdn.net/topics/391819719),非常感謝,我這里就只好換台電腦來運行ASP.NET管理工具網站了。

 WCF服務端配置如下:

<connectionStrings>
  <add name="SqlConn" connectionString="Server=.;Database=aspnetdb;Uid=sa;Pwd=www.zuowenjun.cn;"/>
</connectionStrings>
<system.web>
  <compilation debug="true" targetFramework="4.5" />
  <httpRuntime targetFramework="4.5"/>
  <membership defaultProvider="SqlMembershipProvider">
    <providers>
      <clear/>
      <add name="SqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SqlConn" applicationName="/" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" passwordFormat="Hashed"/>
    </providers>
  </membership>
</system.web>
<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="Service1Behavior">
        <serviceCredentials>
          <serviceCertificate findValue="ZwjCert" storeLocation="LocalMachine"
            storeName="TrustedPeople" x509FindType="FindBySubjectName" />
          <userNameAuthentication userNamePasswordValidationMode="MembershipProvider"
            membershipProviderName="SqlMembershipProvider" />
        </serviceCredentials>
         <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="false" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <bindings>
    <wsHttpBinding>
      <binding name="Service1Binding">
        <security mode="Message">
          <message clientCredentialType="UserName"/>
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
  <service name="WcfService1.Service1" behaviorConfiguration="Service1Behavior">
    <endpoint address="" binding="wsHttpBinding" contract="WcfService1.IService1" bindingConfiguration="Service1Binding">
    </endpoint>
  </service>
</services>
</system.serviceModel>

這里需注意幾點:

1.配置connectionString,連接到membership所需的數據庫; 

2.配置membership,增加SqlMembershipProvider屬性配置;

3.配置serviceCredential,與第一種基本相同,不同的是userNameAuthentication的配置:userNamePasswordValidationMode="MembershipProvider",membershipProviderName="SqlMembershipProvider";

4.配置Binding節點<message clientCredentialType="UserName"/>,這與第一種相同; 

客戶端引用WCF服務,查看生成的配置文件內容,需確保Binding節點有以下配置信息:

<security mode="Message">
        <message clientCredentialType="UserName" />
</security>

最后使用WCF服務,使用代碼與第一種相同,唯一需要注意的是,傳入的UserName和Password均為ASP.NET網站管理工具中創建的用戶信息。

另外我們也可以采用membership+Form驗證,利用ASP.NET的身份驗證機制,要實現這種模式,是需要采用svc文件,並寄宿在IIS上,具體實現方法,參見:http://www.cnblogs.com/danielWise/archive/2011/01/30/1947912.html

由於WCF的驗證方法很多,本文無法一次性全部寫完,敬請期待續篇!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM