在接下来的系列文章中我们正是讨论关于身份认证的主题。在前面我们已经谈到了,WCF中的认证属于“双向认证”,既包括服务对客户端的认证(以下简称客户端认证),也包括客户端对服务的认证(以下简称服务认证)。客户端认证和服务认证从本质上并没有什么不同,无非都是被认证一方提供相应的用户凭证供对方对自己的身份进行验证。我们先来讨论服务认证,客户端认证放在后续的文章中。
在《从两种安全模式谈起》中,我们对TLS/SSL进行了简单的介绍。我们知道,客户端和服务在为建立安全上下文而进行的协商过程中会验证服务端的X.509证书如否值得信任。对于服务证书的验证实际上可以看成是一种服务认证,或者说TLS/SSL对证书的验证可以看成是WCF服务认证的一个环节。
目录
TLS/SSL与X.509证书
创建基于TLS/SSL的WCF服务
创建X.509证书
服务寄宿
服务调用
改变证书认证模式
一、TLS/SSL与X.509证书
TLS/SSL是实现Transport安全模式的一种主要的方式,但不是唯一方式。对于所有基于HTTP的绑定(主要指BasicHttpBinding、WSHttpBinding和WS2007HttpBinding,而WSDualHttpBinding不支持Transport安全模式),如果选择了Transport或者Mixed安全模式,不论采用怎样的认证方式,底层的实现总是基于TLS/SSL(HTTPS)。
而对于NetTcpBinding来说,如果采用Transport安全模式,并且采用非Windows认证(客户端凭证类型选择None或者Certificate),最终的传输安全的实现也是基于TLS/SSL(SSL Over TCP)。如果选择Mixed安全模式,不论选择怎样的客户端凭证类型,WCF最终都会采用TLS/SSL来提供对传输安全的实现。也正是因为如此,在这两种情况下,你总是需要选择一个X.509证书作为服务的凭证。举个例子,对于如下的配置,终结点采用NetTcpBinding绑定,并且选择Transport安全模式,但是却采用匿名的认证方式(客户端凭证类型为None)。
1: <system.serviceModel>
2: <bindings>
3: <netTcpBinding>
4: <binding name="transportTcpBinding">
5: <security mode="Transport">
6: <transport clientCredentialType="None"/>
7: </security>
8: </binding>
9: </netTcpBinding>
10: </bindings>
11: <services>
12: <service name="Artech.WcfServices.Services.CalculatorService">
13: <endpoint address="net.tcp://127.0.0.1/calculatorservice"
14: binding="netTcpBinding" bindingConfiguration="transportTcpBinding"
15: contract="Artech.WcfServices.Contracts.ICalculator" />
16: </service>
17: </services>
18: </system.serviceModel>
在对服务进行寄宿时,会抛出如下图所示的InvalidOperationException异常,提示“未提供服务证书。请在 ServiceCredentials 中指定服务证书”。
作为服务凭证的证书通过服务行为ServiceCredentials来指定,对于WCF的安全体系来说,ServiceCredentials是个非常重要的对象,在本章后续文章中我们将反复地使用到它。对于服务凭证的指定,需要使用到ServiceCredentials的只读属性ServiceCertificate,该属性对应的类型为X509CertificateRecipientServiceCredential。X509CertificateRecipientServiceCredential对象实际上是对一个X509Certificate2对象的封装,它定义了若干SetCertificate方法重载用以指定一个X.509证书作为服务的凭证。ServiceCredentials和X509CertificateRecipientServiceCredential的相关定义反映在如下所示的代码片断中。
1: public class ServiceCredentials : SecurityCredentialsManager, IServiceBehavior
2: {
3: //其他成员
4: public X509CertificateRecipientServiceCredential ServiceCertificate { get; }
5: }
6: public sealed class X509CertificateRecipientServiceCredential
7: {
8: //其他成员
9: public void SetCertificate(string subjectName);
10: public void SetCertificate(string subjectName, StoreLocation storeLocation, StoreName storeName);
11: public void SetCertificate(StoreLocation storeLocation, StoreName storeName, X509FindType findType, object findValue);
12:
13: public X509Certificate2 Certificate { get; set; }
14: }
如果采用自我寄宿的方式,我们可以通过编程的方式来为寄宿的服务设置一个代表服务凭证的X.509证书。在下面给出的代码片断中,我们为服务设置一个主体名称为Jinnan-PC(我的机器名)的X.509证书,该证书是一个基于个人存储(Personal Store,通过StoreName.My表示)的本机(StoreLocation.LocalMachine)证书。
1: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
2: {
3: ServiceCredentials serviceCredentials = host.Description.Behaviors.Find<ServiceCredentials>();
4: if (null == serviceCredentials)
5: {
6: serviceCredentials = new ServiceCredentials();
7: host.Description.Behaviors.Add(serviceCredentials);
8: }
9: serviceCredentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "Jinnan-PC");
10: host.Open();
11: ...
12: }
当然,我们依旧推荐采用配置的方式进行服务凭证的设置。对于上面一段设置服务证书的代码,我们可以通过下面的一段配置来代替。
1: <system.serviceModel>
2: ...
3: <services>
4: <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="serviceCertificateBehavior">
5: <endpoint address="net.tcp://127.0.0.1/calculatorservice"
6: binding="netTcpBinding" bindingConfiguration="transportTcpBinding" contract="Artech.WcfServices.Contracts.ICalculator" />
7: </service>
8: </services>
9: <behaviors>
10: <serviceBehaviors>
11: <behavior name="serviceCertificateBehavior">
12: <serviceCredentials>
13: <serviceCertificate storeLocation="LocalMachine" storeName="My"
14: x509FindType="FindBySubjectName" findValue="Jinnan-PC" />
15: </serviceCredentials>
16: </behavior>
17: </serviceBehaviors>
18: </behaviors>
19: </system.serviceModel>
对于采用基于TLS/SSL的Transport安全模式,对服务证书的验证方式会因为绑定类型的不同而具有小小的差异。
二、创建基于TLS/SSL的WCF服务
接下来我们会通过一个简单的例子来演示如何在WCF服务中使用基于TLS/SSL的Transport安全。该实例会涉及两种不同的绑定类型(WS2007HttpBinding和NetTcpBinding)和寄宿方式(自我寄宿和IIS寄宿)。
我们还是采用惯用的计算服务的例子,演示实例的解决方式具有右图所示的结构。Contract和Services为两个类库项目,分别用于定义服务契约和实现契约的服务类型。而Hosting和Client为两个控制台应用,前者用于进行服务寄宿(自我寄宿),后者用于模拟客户端程序。下面的代码片断代码了分别定义在Contracts和Services项目中的服务契约接口ICalculator和具体的服务类型CalculatorService。
1: using System.ServiceModel;
2: namespace Artech.WcfServices.Contracts
3: {
4: [ServiceContract(Namespace = "http://www.artech.com/")]
5: public interface ICalculator
6: {
7: [OperationContract]
8: double Add(double x, double y);
9: }
10: }
11:
12: using Artech.WcfServices.Contracts;
13: namespace Artech.WcfServices.Services
14: {
15: public class CalculatorService : ICalculator
16: {
17: public double Add(double x, double y)
18: {
19: return x + y;
20: }
21: }
22: }
创建X.509证书
由于TLS/SSL需要通过协商的方式生成一个用于消息签名和加密的会话密钥,而会话密钥的交换依赖一个X.509证书以确保安全。所以我们首要的任务是需要得到一个X.509证书,这样一个证书可以直接借助于MakeCert工具,通过命令行的方式创建一个主体名称为Jinnan-PC(我个人的机器名,你需要替换成你本机的名称)的证书。为了方便,我们在测试的时候倾向于创建自签名证书,即证书授予者和颁发者身份合二为一。不过为了演示证书正常的信任链,我们不采用这种方式。所以我们需要通过运行如下的命令行先创建一个CA证书。该CA证书本身是自签名的(对应于-r命令行开关)
1: Makecert -n "CN=RootCA" -r -sv C:\RootCA.pvk C:\RootCA.cer
上面的命令行在执行的过程中,会弹出两个用于输入密码的对话框。你需要输入相应的密码用以包括生成的两个文件,一个是包含私钥的文件RootCA.pvk,另一个是证书文件RootCA.cer,它们都保存在C盘根目录下。
然后通过如下的命令行创建一个主题名称为Jinnan-PC(我的机器名,你需要换成你的机器名或者本机影射的Host Name)的证书,并以上面创建证书对应的CA(RootCA)作为该证书的颁发者(-ic C:\RootCA.cer -iv C:\RootCA.pvk)。该证书最终自动保存到本机(-sr LocalMachine)的个人存储区(-ss My)。而-pe表示证书的私钥可以被导出。-sky表示密钥的类型或者作用,具有两个选项signature和exchange,前者用于数字签名,后者用于加密和密钥交换,这里选用exchange。
1: Makecert -n "CN=Jinnan-PC" -ic C:\RootCA.cer -iv C:\RootCA.pvk -sr LocalMachine -ss My -pe -sky exchange
服务寄宿
我们先使用NetTcpBinding作为绑定,在Hosting项目中定义如下的配置。从配置中我们可以看到:寄宿的CalculatorService服务唯一的终结点使用了Transport模式的NetTcpBinding绑定。该绑定的客户端凭证类型为None,意味着接受匿名客户端。通过命令行生成和存储的X.509证书通过服务行为的方式被设置成寄宿服务的凭证。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <bindings>