zoukankan      html  css  js  c++  java
  • 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", "xuyue1000@hotmail.com");
    }
    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: 服务调用失败...

  • 相关阅读:
    1104 Sum of Number Segments (20 分)(数学问题)
    1092 To Buy or Not to Buy (20 分)(hash散列)
    1082 Read Number in Chinese (25 分)(字符串处理)【背】
    1105 Spiral Matrix (25 分)(模拟)
    初识网络安全及搭建网站(内网)
    HTML5开发者需要了解的技巧和工具汇总(转)
    native+web开发模式之web前端经验分享
    移动平台3G手机网站前端开发布局技巧汇总(转)
    Asp.net 中图片存储数据库以及页面读取显示通用方法详解附源码下载
    使用H3Viewer来查看VS2010的帮助文档
  • 原文地址:https://www.cnblogs.com/aiwz/p/6154625.html
Copyright © 2011-2022 走看看