第九篇:WCF安全 - 自定义认证

接着上一篇,我们尝试一下用自定义用户名密码的方式来做安全认证,这样就不用受制于Windows的用户系统了。

首先需要说明的是,使用自定义用户名密码时,由于不能利用Windows用户系统的相关安全机制了,因此必须自己准备数字证书来处理数据加密。

1、准备数字证书

证书要求有可进行密钥交换的私钥,一般用makcert比较方便,自带的,也可以用OpenSSL或OpenSSL.Net一类的。本例中生成一个自签名的根证书,放入系统的受信根证书颁发机构区:

 
  1. makecert -n "CN=192.168.90.81" -b 01/01/2012 -e 01/01/2050 -r -sky exchange -sr LocalMachine -ss Root -a sha1

  2. 参数含义:

  3.  -n 指定使用者为192.168.90.81,注意它必须和调用时指定的域名一致

  4.  -b 起始日期为2012-1-1

  5.  -e 失效日期为2050-1-1

  6.  -r 自签名

  7.  -sky exchange 指定是密钥交换型而不是签名型

  8.  -sr LocalMachine 存入本地计算机

  9.  -ss Root 存入受信任根证书颁发机构区

  10.  -a sha1 使用SHA1签名

2、服务端

服务端要修改几处,首先是配置文件App.config:

 
  1. <?xmlversion="1.0"encoding="utf-8"?>

  2. <configuration>

  3. <system.serviceModel>

  4. <services>

  5.      <!--在上一篇的基础上加了behaviorConfiguration,内容见后-->

  6. <servicename="Server.DataProvider"behaviorConfiguration="tcpBehavior">

  7. <endpointaddress=""binding="netTcpBinding"contract="Server.IData"bindingConfiguration="tcpBinding"/>

  8. <host>

  9. <baseAddresses>

  10. <addbaseAddress="net.tcp://localhost:8081/wcf"/>

  11. </baseAddresses>

  12. </host>

  13. </service>

  14. </services>

  15. <bindings>

  16. <netTcpBinding>

  17. <bindingname="tcpBinding">

  18. <securitymode="Message">

  19.            <!--与上一篇相比,认证类型从Windows改成了UserName-->

  20. <messageclientCredentialType="UserName"/>

  21. </security>

  22. </binding>

  23. </netTcpBinding>

  24. </bindings>

  25.    <!--这是新加的节,用于指定用户名密码的验证方式-->

  26. <behaviors>

  27. <serviceBehaviors>

  28.        <!--注意这个name是被前面使用的-->

  29. <behaviorname="tcpBehavior">

  30. <serviceCredentials>

  31.            <!--指定验证方式为Custom,表示自定义,既然是自定义的,就要指出用哪个类进行用户名密码验证,这里指定了Server程序集中的Server.Validator类,注意这里类完整名称的写法-->

  32. <userNameAuthenticationuserNamePasswordValidationMode="Custom"customUserNamePasswordValidatorType="Server.Validator, Server"/>

  33.            <!--指定用于数据加密的证书,LocalMachine表示本地计算机,Root表示受信任根证书颁发机构,192.168.90.81是证书标题(因为做证书时没指定标题,所以使用者默认就是标题),FindBySubjectName表示按标题查找-->

  34. <serviceCertificatestoreLocation="LocalMachine"storeName="Root"findValue="192.168.90.81"x509FindType="FindBySubjectName"/>

  35. </serviceCredentials>

  36. </behavior>

  37. </serviceBehaviors>

  38. </behaviors>

  39. </system.serviceModel>

  40. </configuration>

接下来是自定义的验证类,增加一个Validator类,它要继承System.IdentityModel.Selector.UserNamePasswordValidator基类。

 
  1. using System;

  2. using System.IdentityModel.Selectors;

  3. using System.ServiceModel;

  4. namespace Server

  5. {

  6. publicclass Validator : UserNamePasswordValidator

  7.    {

  8. //重写Validate方法,这里简化处理,直接写死用户名密码,实际应用中应结合DB、配置文件等来做验证

  9. publicoverridevoid Validate(string userName, string password)

  10.        {

  11. if(!string.Equals(userName, "root") || !string.Equals(password, "pass"))

  12. thrownew Exception("Access Denied");

  13.        }

  14.    }

  15. }

最后我们还要小改一下契约接口的实现类,因为之前我们用WindowIdentity来识别登录用户,换用自定义用户名密码后,它不管用了,所以新的实现类是这样的:

 
  1. using System;

  2. using System.ServiceModel;

  3. namespace Server

  4. {

  5.    [ServiceBehavior]

  6. publicclass DataProvider : IData

  7.    {

  8. publicstring SayHello()

  9.        {

  10. //变化不大,用PrimaryIdentity来代替WindowIdentity

  11. returnstring.Format("Hello {0}", OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name);

  12.        }

  13.    }

  14. }

OK,运行一下,应该能正常启动,如果失败,仔细看一下提示信息,一般都是证书问题。

3、客户端

变化不大,先来看配置文件App.config:

 
  1. <?xmlversion="1.0"encoding="utf-8"?>

  2. <configuration>

  3. <system.serviceModel>

  4. <client>

  5. <endpointbinding="netTcpBinding"contract="Server.IData"address="net.tcp://192.168.90.81:8081/wcf"name="DataProvider"bindingConfiguration="tcp"/>

  6. </client>

  7. <bindings>

  8. <netTcpBinding>

  9. <bindingname="tcp">

  10. <securitymode="Message">

  11.            <!--只有此处把Windows改成了UserName,和服务端对应-->

  12. <messageclientCredentialType="UserName"/>

  13. </security>

  14. </binding>

  15. </netTcpBinding>

  16. </bindings>

  17. </system.serviceModel>

  18. </configuration>

然后是调用时用户名密码的传递方式变了一点:

 
  1. using System;  

  2. using System.ServiceModel;  

  3. using System.ServiceModel.Channels;  

  4. namespace Client  

  5. {  

  6. class Program  

  7.    {  

  8. staticvoid Main(string[] args)  

  9.        {  

  10. //创建一个ChannelFactory,指定使用名为DataProvider的配置

  11.            var factory = new ChannelFactory<Server.IData>("DataProvider");  

  12. //指定用户名、密码,和前一篇的区别是把Windows换成了UserName

  13.            factory.Credentials.UserName.UserName = "root";

  14.            factory.Credentials.UserName.Password = "pass";

  15. //创建Channel,并调用SayHello方法

  16.            var proxy = factory.CreateChannel();  

  17.            Console.WriteLine(proxy.SayHello());  

  18.            ((IChannel)proxy).Close();  

  19.        }  

  20.    }  

  21. }  

一切就绪,运行一下吧,应该能看到“Hello root”。如果用户名密码错误,会收到Exception。

如果服务端启动失败,请检查:

  ◇ 证书是否有可交换的密钥

◇ 证书是否正确导入了系统
按服务端App.config中指定的证书查找方式是否可找到证书
指定的自定义验证类名称是否错误

如果客户端访问失败,请检查:

  ◇ 是否提供了正确的用户名密码

  ◇ 服务端证书有效期是否合法
  ◇ 服务端证书的证书链是否完整
  ◇ 客户端访问时使用的域名/IP是否与服务端证书的使用者一致

OK,安全问题就讲到这里吧,既然是简单教程,就不继续深入了。

本文出自 “” 博客,请务必保留此出处