第九篇:WCF安全 - 自定义认证
接着上一篇,我们尝试一下用自定义用户名密码的方式来做安全认证,这样就不用受制于Windows的用户系统了。
首先需要说明的是,使用自定义用户名密码时,由于不能利用Windows用户系统的相关安全机制了,因此必须自己准备数字证书来处理数据加密。
1、准备数字证书
证书要求有可进行密钥交换的私钥,一般用makcert比较方便,自带的,也可以用OpenSSL或OpenSSL.Net一类的。本例中生成一个自签名的根证书,放入系统的受信根证书颁发机构区:
- makecert -n "CN=192.168.90.81" -b 01/01/2012 -e 01/01/2050 -r -sky exchange -sr LocalMachine -ss Root -a sha1
- 参数含义:
- -n 指定使用者为192.168.90.81,注意它必须和调用时指定的域名一致
- -b 起始日期为2012-1-1
- -e 失效日期为2050-1-1
- -r 自签名
- -sky exchange 指定是密钥交换型而不是签名型
- -sr LocalMachine 存入本地计算机
- -ss Root 存入受信任根证书颁发机构区
- -a sha1 使用SHA1签名
2、服务端
服务端要修改几处,首先是配置文件App.config:
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <services>
- <!--在上一篇的基础上加了behaviorConfiguration,内容见后-->
- <service name="Server.DataProvider" behaviorConfiguration="tcpBehavior">
- <endpoint address="" binding="netTcpBinding" contract="Server.IData" bindingConfiguration="tcpBinding" />
- <host>
- <baseAddresses>
- <add baseAddress="net.tcp://localhost:8081/wcf" />
- </baseAddresses>
- </host>
- </service>
- </services>
- <bindings>
- <netTcpBinding>
- <binding name="tcpBinding">
- <security mode="Message">
- <!--与上一篇相比,认证类型从Windows改成了UserName-->
- <message clientCredentialType="UserName" />
- </security>
- </binding>
- </netTcpBinding>
- </bindings>
- <!--这是新加的节,用于指定用户名密码的验证方式-->
- <behaviors>
- <serviceBehaviors>
- <!--注意这个name是被前面使用的-->
- <behavior name="tcpBehavior">
- <serviceCredentials>
- <!--指定验证方式为Custom,表示自定义,既然是自定义的,就要指出用哪个类进行用户名密码验证,这里指定了Server程序集中的Server.Validator类,注意这里类完整名称的写法-->
- <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Server.Validator, Server" />
- <!--指定用于数据加密的证书,LocalMachine表示本地计算机,Root表示受信任根证书颁发机构,192.168.90.81是证书标题(因为做证书时没指定标题,所以使用者默认就是标题),FindBySubjectName表示按标题查找-->
- <serviceCertificate storeLocation="LocalMachine" storeName="Root" findValue="192.168.90.81" x509FindType="FindBySubjectName" />
- </serviceCredentials>
- </behavior>
- </serviceBehaviors>
- </behaviors>
- </system.serviceModel>
- </configuration>
接下来是自定义的验证类,增加一个Validator类,它要继承System.IdentityModel.Selector.UserNamePasswordValidator基类。
- using System;
- using System.IdentityModel.Selectors;
- using System.ServiceModel;
- namespace Server
- {
- public class Validator : UserNamePasswordValidator
- {
- //重写Validate方法,这里简化处理,直接写死用户名密码,实际应用中应结合DB、配置文件等来做验证
- public override void Validate(string userName, string password)
- {
- if(!string.Equals(userName, "root") || !string.Equals(password, "pass"))
- throw new Exception("Access Denied");
- }
- }
- }
最后我们还要小改一下契约接口的实现类,因为之前我们用WindowIdentity来识别登录用户,换用自定义用户名密码后,它不管用了,所以新的实现类是这样的:
- using System;
- using System.ServiceModel;
- namespace Server
- {
- [ServiceBehavior]
- public class DataProvider : IData
- {
- public string SayHello()
- {
- //变化不大,用PrimaryIdentity来代替WindowIdentity
- return string.Format("Hello {0}", OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name);
- }
- }
- }
OK,运行一下,应该能正常启动,如果失败,仔细看一下提示信息,一般都是证书问题。
3、客户端
变化不大,先来看配置文件App.config:
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <system.serviceModel>
- <client>
- <endpoint binding="netTcpBinding" contract="Server.IData" address="net.tcp://192.168.90.81:8081/wcf" name="DataProvider" bindingConfiguration="tcp" />
- </client>
- <bindings>
- <netTcpBinding>
- <binding name="tcp">
- <security mode="Message">
- <!--只有此处把Windows改成了UserName,和服务端对应-->
- <message clientCredentialType="UserName" />
- </security>
- </binding>
- </netTcpBinding>
- </bindings>
- </system.serviceModel>
- </configuration>
然后是调用时用户名密码的传递方式变了一点:
- using System;
- using System.ServiceModel;
- using System.ServiceModel.Channels;
- namespace Client
- {
- class Program
- {
- static void Main(string[] args)
- {
- //创建一个ChannelFactory,指定使用名为DataProvider的配置
- var factory = new ChannelFactory<Server.IData>("DataProvider");
- //指定用户名、密码,和前一篇的区别是把Windows换成了UserName
- factory.Credentials.UserName.UserName = "root";
- factory.Credentials.UserName.Password = "pass";
- //创建Channel,并调用SayHello方法
- var proxy = factory.CreateChannel();
- Console.WriteLine(proxy.SayHello());
- ((IChannel)proxy).Close();
- }
- }
- }
一切就绪,运行一下吧,应该能看到“Hello root”。如果用户名密码错误,会收到Exception。
如果服务端启动失败,请检查:
◇ 证书是否有可交换的密钥
◇ 证书是否正确导入了系统
◇ 按服务端App.config中指定的证书查找方式是否可找到证书
◇ 指定的自定义验证类名称是否错误
如果客户端访问失败,请检查:
◇ 是否提供了正确的用户名密码
◇ 服务端证书有效期是否合法
◇ 服务端证书的证书链是否完整
◇ 客户端访问时使用的域名/IP是否与服务端证书的使用者一致
OK,安全问题就讲到这里吧,既然是简单教程,就不继续深入了。
本文转自 BoyTNT 51CTO博客,原文链接:http://blog.51cto.com/boytnt/818443,如需转载请自行联系原作者