本文共 4171 字,大约阅读时间需要 13 分钟。
第九篇: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>
-
- <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">
-
- <message clientCredentialType="UserName" />
- </security>
- </binding>
- </netTcpBinding>
- </bindings>
-
-
- <behaviors>
- <serviceBehaviors>
-
- <behavior name="tcpBehavior">
- <serviceCredentials>
- <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Server.Validator, Server" />
- <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
- {
-
- 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()
- {
-
- 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">
-
- <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)
- {
-
- var factory = new ChannelFactory<Server.IData>("DataProvider");
-
-
- factory.Credentials.UserName.UserName = "root";
- factory.Credentials.UserName.Password = "pass";
-
-
- var proxy = factory.CreateChannel();
- Console.WriteLine(proxy.SayHello());
- ((IChannel)proxy).Close();
- }
- }
- }
一切就绪,运行一下吧,应该能看到“Hello root”。如果用户名密码错误,会收到Exception。
如果服务端启动失败,请检查:
◇ 证书是否有可交换的密钥 ◇ 证书是否正确导入了系统 ◇ 按服务端App.config中指定的证书查找方式是否可找到证书 ◇ 指定的自定义验证类名称是否错误
如果客户端访问失败,请检查:
◇ 是否提供了正确的用户名密码 ◇ 服务端证书有效期是否合法 ◇ 服务端证书的证书链是否完整 ◇ 客户端访问时使用的域名/IP是否与服务端证书的使用者一致
OK,安全问题就讲到这里吧,既然是简单教程,就不继续深入了。
转载地址:http://vfuca.baihongyu.com/