WCF的用户名+密码认证方式

 

概述

今天在做Master Data Service(后面简称MDS)项目时需要通过WCF来使用MDS的API,从而对MDS的数据进行操作。在这个过程中,遇到了一个棘手的问题,就是在客户端调用Web Service时的身份认证问题,于是乎对WCF的认证方式做了一个简单的了解。在这里还要感谢蔡总陪我加班一起解决问题,在蔡总的大力支持下问题得以解决。我们解决问题的方式采用了客户端用户名+密码的方式来进行身份认证,这只是诸多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 {getset; }  
  10.     public string UserName { getset; }  
  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 { getset; }  
  10.     public MembershipProvider MembershipProvider { getset; }  
  11.     public UserNamePasswordValidationMode UserNamePasswordValidationMode { getset;  
  12.   
  13. }  

通过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。

  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(本例所示的配置也是基于该版本),则还需额外添加对System.Web.ApplicationServices.dll的引用。接下来,需要在服务寄宿方面所做的工作就是将下面一段配置整个拷贝到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,  
  10. PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="AspNetDb" applicationName="MembershipAuthenticationDemo"  
  11. requiresQuestionAndAnswer="false"/>  
  12.       </providers>  
  13.     </membership>  
  14.   </system.web>  
  15.   <system.serviceModel>  
  16.     <bindings>  
  17.       <ws2007HttpBinding>  
  18.         <binding name="userNameCredentialBinding">  
  19.           <security mode="Message">  
  20.             <message clientCredentialType="UserName"/>  
  21.           </security>  
  22.         </binding>  
  23.       </ws2007HttpBinding>  
  24.     </bindings>  
  25.     <services>  
  26.       <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="membershipAuthentication">  
  27.         <endpoint address="http://127.0.0.1/calculatorservice" binding="ws2007HttpBinding"  
  28. bindingConfiguration="userNameCredentialBinding" contract="Artech.WcfServices.Contracts.ICalculator"/>  
  29.       </service>  
  30.     </services>  
  31.     <behaviors>  
  32.       <serviceBehaviors>  
  33.         <behavior  name="membershipAuthentication">  
  34.           <serviceCredentials>  
  35.             <serviceCertificate storeLocation="LocalMachine" storeName ="My" x509FindType="FindBySubjectName" findValue="YueXu-PC"/>  
  36.             <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="myProvider"/>  
  37.           </serviceCredentials>  
  38.         </behavior>  
  39.       </serviceBehaviors>  
  40.     </behaviors>  
  41.   </system.serviceModel>  
  42. </configuration>  

至此,在我们创建的数据库中并没有用户帐户记录。为了演示认证效果,我们需要创建相关用户帐户记录。为了方便,我直接将相关的代码写在了服务寄宿的代码中。如下面的代码片断所示,在对服务进行寄宿之前,我通过调用Membership的静态方法CreateUser创建了一个用户名、密码和Email分别为xuyue、password01和xuyue1000@hotmail的帐号。

  1. if (Membership.FindUsersByName("xuyue").Count == 0)  
  2. {  
  3.     Membership.CreateUser("xuyue""password01""xuyue1000@hotmail.com");  
  4. }  
  5. using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))  
  6. {  
  7.     host.Open();  
  8.     Console.Read();  
  9. }  

接下来我们需要对客户端的配置进行相应的调整,整个配置内容如下面的XML片断所示。对于这段配置有一点需要注意的是:终结点应用了一个名称为peerTrustSvcCertValidation的行为,该行为中将服务证书认证模式设置成PeerTrust,所以你需要通过MMC证书管理单元的导出/导入功能将YueXu-PC证书导入到“受信任人(Trusted People)”存储区。

  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"  
  15. address=<a href="http://127.0.0.1/calculatorservice">http://127.0.0.1/calculatorservice</a> binding="ws2007HttpBinding"  
  16. bindingConfiguration="userNameCredentialBinding" contract="Artech.WcfServices.Contracts.ICalculator">  
  17.         <identity>  
  18.           <certificateReference storeLocation="LocalMachine" storeName ="My" x509FindType="FindBySubjectName" findValue="YueXu-PC"/>  
  19.         </identity>  
  20.       </endpoint>  
  21.     </client>  
  22.     <behaviors>  
  23.       <endpointBehaviors>  
  24.         <behavior name="peerTrustSvcCertValidation">  
  25.           <clientCredentials>  
  26.             <serviceCertificate>  
  27.               <authentication certificateValidationMode="PeerTrust"/>  
  28.             </serviceCertificate>  
  29.           </clientCredentials>  
  30.         </behavior>  
  31.       </endpointBehaviors>  
  32.     </behaviors>  
  33.   </system.serviceModel>  
  34. </configuration>  

最后,我们来编写如下一段客户端进行服务调用的程序。在下面的代码中,我进行了两次服务调用。但是创建服务代理对象的ChannelFactory<ICalculator>被设置了不同的用户名凭证。其中第一个是正确的用户名和密码,后一个却指定了一个根本不存在的用户名。

  1. using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService"))  
  2. {  
  3.     UserNamePasswordClientCredential credential = channelFactory.Credentials.UserName;  
  4.     credential.UserName     = "xuyue";  
  5.     credential.Password     = "password01";  
  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     = "wrongName";  
  14.     credential.Password     = "wrongPWD";  
  15.     ICalculator calculator  = channelFactory.CreateChannel();  
  16.     try  
  17.     {  
  18.         calculator.Add(1, 2);  
  19.     }  
  20.     catch  
  21.     {  
  22.         Console.WriteLine("服务调用失败...");  
  23.     }  
  24. }  

输出结果:

1: 服务调用成功...

2: 服务调用失败...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星火燎猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值