zoukankan      html  css  js  c++  java
  • 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 FilesIIS Express
    iisexpress.exe /path:C:WindowsMicrosoft.NETFrameworkv4.0.30319ASP.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:WindowsMicrosoft.NETFrameworkv4.0.30319System.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的验证方法很多,本文无法一次性全部写完,敬请期待续篇!

  • 相关阅读:
    网站安全编程 黑客入侵 脚本黑客 高级语法入侵 C/C++ C# PHP JSP 编程
    【算法导论】贪心算法,递归算法,动态规划算法总结
    cocoa2dx tiled map添加tile翻转功能
    8月30日上海ORACLE大会演讲PPT下载
    【算法导论】双调欧几里得旅行商问题
    Codeforces Round #501 (Div. 3) B. Obtaining the String (思维,字符串)
    Codeforces Round #498 (Div. 3) D. Two Strings Swaps (思维)
    Educational Codeforces Round 89 (Rated for Div. 2) B. Shuffle (数学,区间)
    洛谷 P1379 八数码难题 (BFS)
    Educational Codeforces Round 89 (Rated for Div. 2) A. Shovels and Swords (贪心)
  • 原文地址:https://www.cnblogs.com/zhaoshujie/p/9760874.html
Copyright © 2011-2022 走看看