整个安全传输是在WCF的信道层进行的,而绑定是信道层的缔造者,所以终结点采用哪种类型的绑定以及对绑定的属性进行怎样的设置决定了信道层最终采用何种机制实现消息的安全传输。具体来说,我们可以通过绑定设置最终采用的安全模式,以及基于相应安全模式下进行认证和消息保护的行为。
一、Binding安全相关的应用编程接口
不同的绑定类型由于其采用的传输协议不同,应用的场景也各有侧重,很难提供一种统一的应用编程接口完成基于不同绑定的安全设置,所以每一种绑定都具有各自用于安全设置相关的类型。但是基于对安全的设置,大部分系统预定义绑定(不是所有)都具有类似于如下代码片断所示的属性定义。
1: Public class XxxBinding
2: {
3: Public XxxSecurity Security {}
4: }
5: Public class XxxSecurity
6: {
7: Public XxxSecurityMode Mode{get;set;}
8: Public XxxTransportSecurity Transport{get;set;}
9: Public XxxMessageSecurity Message{get;set;}
10: }
对于某个绑定XxxBinding(Xxx泛指某种绑定类型,所有带Xxx前缀的类型并不意味着它们代表完全一样的字符),它具有一个专属的XxxSecurity类型的Security属性。而这个XxxSecurity类型一般具有三个属性:Mode表示采用的安全模式,而Transport和Message用于针对Transport和Message安全模式下的设置。
对于围绕着绑定进行的安全设置,我们首要的任务就是制定采用的安全模式。在安全模式确定之后,客户端凭证的选择决定了认证方最终采用怎样的认证机制。接下来,我们就来谈谈针对不各种常用的系统预定义绑定,安全模式和基于安全模式的客户端凭证如何设置。先从BasicHttpBinding谈起。
二、BasicHttpBinding支持的安全模式
下面的代码片断表示BasicHttpBinding安全相关应用编程接口的定义,这和上面给出的“泛型绑定”的定义完全一致。通过Security属性返回用于进行针对BasicHttpBinding安全设置的类型为BasicHttpSecurity。BasicHttpSecurity的Mode、Transport和Message三种属性的类型分别为BasicHttpSecurityMode、HttpTransportSecurity和BasicHttpMessageSecurity。
1: public class BasicHttpBinding : Binding, IBindingRuntimePreferences
2: {
3: //其他成员
4: public BasicHttpSecurity Security { get; set; }
5: }
6: public sealed class BasicHttpSecurity
7: {
8: //其他成员
9: public BasicHttpSecurityMode Mode { get; set; }
10: public HttpTransportSecurity Transport { get; set; }
11: public BasicHttpMessageSecurity Message { get; set; }
12: }
枚举BasicHttpSecurityMode中定义了BasicHttpBinding支持的5中安全模式。其中None为默认选项,表示并不采用任何安全机制,Transport、Message和TransportWithMessageCredential分别表示之前介绍的Transport、Message和Mixed安全模式。TransportWithMessageCredential表示“使用基于Message模式凭证的Transport模式”。由于Mixed安全模式通过Message模式实现对客户端的认证,所以要求客户端采用基于Message模式的凭证。而除客户端认证的其他安全要素的实现则都是采用Transport模式。所以TransportWithMessageCredential在BasicHttpSecurityMode枚举中的表示和我们讲的Mixed模式从语义上讲是一致的。TransportCredentialOnly是BasicHttpBinding所独有的安全模式。它只提供针对于HTTP的客户端认证,并不能提供消息一致性和机密性的保证。
1: public enum BasicHttpSecurityMode
2: {
3: None,
4: Transport,
5: Message,
6: TransportWithMessageCredential,
7: TransportCredentialOnly
8: }
三、基于Transport模式的客户端凭证
HttpTransportSecurity用于进行针对Transport模式下的安全设置。而通过ClientCredentialType属性,我们可以设置客户端凭证的类型。该属性类型为HttpClientCredentialType枚举,定义其中的六个枚举值表示支持的六种基于Tranport模式的客户端凭证类型。HttpTransportSecurity和HttpClientCredentialType相关定义如下。
1: public sealed class HttpTransportSecurity
2: {
3: //其他成员
4: public HttpClientCredentialType ClientCredentialType {get; set; }
5: }
6: public enum HttpClientCredentialType
7: {
8: None,
9: Basic,
10: Digest,
11: Ntlm,
12: Windows,
13: Certificate
14: }
定义在枚举类型HttpClientCredentialType中的六种不同的客户端用户凭证类型体现了服务端针对客户端不同的认证方式:
- None:客户端无需指定用户凭证,即匿名认证。此为默认值;
- Basic:采用Basic认证方式进行客户端认证。在这种认证方式下,客户端需要提供有效的用户名和密码,但是仅仅采用较弱的方式对密码进行加密。所以当且仅当你确定客户端和服务端之间的连接绝对安全的前提下,你才能用这种认证方式;
- Digest:采用Digest认证方式进行客户端认证。Digest认证提供与Basic一样的认证功能,但是在安全性上有所提升。主要体现并不是直接将用户名和密码直接进行网络传输,而是对其进行哈希计算(MD5)得到一个哈希码(此过程又称为Message Digest),最终传输的是该哈希码;
- Ntlm:表示使用基于NTLM方式的Windows集成认证方式对客户端进行认证;
- Windows:表示使用Windows集成认证的方式对客户端进行认证。如果能够使用Kerberos,则直接采用Kerberos进行认证,否则才使用NTLM;
- Certificate:表示客户端的身份通过一个X.509数字证书表示,服务端通过校验证书的方式来确定客户端的真实身份。
无论是在进行服务寄宿的时候为ServiceHost添加终结点,还是在客户端创建调用服务的终结点,都可以通过编程的方式来设置绑定的安全模式和客户端用于凭证类型。如下面的代码片断所示,我们为BasicHttpBinding设置了Transport安全模式,并将其客户端凭证设置成Windows。由于所有基于HTTP的绑定都通过HTTPS来实现Transport安全,所以当选择Transport和TransportWithMessageCredential安全模式的情况下,终结点地址必须是一个HTTPS地址。
服务寄宿代码:
1: using(ServiceHost host = new ServiceHost(typeof(CalculatorService)))
2: {
3: var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
4: binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
5: host.AddServiceEndpoint(typeof(ICalculator), binding, "https://localhost/calculatorservice");
6: host.Open();
7: ...
8: }
服务调用代码:
1: var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
2: binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
3: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(binding, "https://localhost/calculatorservice"))
4: {
5: ICalculator calculator = channelFactory.CreateChannel();
6: double result = calculator.Add(1, 2);
7: ...
8: }
对于包括客户端凭证类型在内的针对绑定的安全设置,我们还是推荐采用配置的方式。基于绑定的配置节中具有一个<security>的字节点,用于进行安全相关的设置。采用的安全模式通过该节点的mode属性设置。而给予Transport模式相关的设置则配置在<security>/<transport>配置节中,其中配置属性clientCredentialType表示客户端凭证类型。在下面的给出的配置片断中,我为寄宿的服务添加了一个采用BasicHttpBinding的终结点,该绑定的模式被设置为Transport,并采用Certificate客户端凭证类型。
1: <system.serviceModel>
2: <bindings>
3: <basicHttpBinding>
4: <binding name="transportBinding">
5: <security mode="Transport">
6: <transport clientCredentialType="Certificate"/>
7: </security>
8: </binding>
9: </basicHttpBinding>
10: </bindings>
11: <services>
12: <service name="Artech.WcfServices.Services.CalculatorService">
13: <endpoint address="https://Jinnan-PC/calculatorservice" binding="basicHttpBinding" bindingConfiguration="transportBinding" contract="Artech.WcfServices.Contracts.ICalculator" />
14: </service>
15: </services>
16: </system.serviceModel>
四、基于Message模式的客户端凭证
类型BasicHttpSecurity用于进行针对于BasicHttpBinding关于Message安全模式的相关设置。你同样可以通过它的ClientCredentialType属性设置客户端凭证类型,该属性类型为System.ServiceModel.BasicHttpMessageCredentialType枚举。定义在BasicHttpMessageCredentialType中的两个枚举值(UserName和Certificate)表示支持的两种客户端凭证类型,它们分别代表基于用户名/密码的凭证和针对X.509证书的凭证。在默认的情况下采用用户名/密码的凭证。BasicHttpMessageSecurity和BasicHttpMessageCredentialType相关定义如下面的代码片断所示。
1: public sealed class BasicHttpMessageSecurity
2: {
3: public BasicHttpMessageCredentialType ClientCredentialType { get; set; }
4: }
5: public enum BasicHttpMessageCredentialType
6: {
7: UserName,
8: Certificate
9: }
关于上述的两种客户端凭证,BasicHttpMessageCredentialType.UserName只能用在Mixed模式下。当你选择了Message模式,则只能选择BasicHttpMessageCredentialType. Certificate。 WCF为什么会具有如此一个限制,你会在后续文章中找到答案。举个例子,我通过如下一段代码对服务CalculatorService进行寄宿,并采用了一个采用Message模式的BasicHttpBinding。
1: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
2: {
3: var binding = new BasicHttpBinding(BasicHttpSecurityMode.Message);
4: host.AddServiceEndpoint(typeof(ICalculator), binding, "http://localhost/calculatorservice");
5: host.Open();
6: ...
7: }
当ServiceHost被开启的时候,如下图所示的InvalidOperationException异常被抛出来,并提示“BasicHttpBinding绑定要求 BasicHttpBinding.Security.Message.ClientCredentialType 等效于安全消息的 BasicHttpMessageCredentialType.Certificate 凭据类型。为 UserName 凭据选择Transport 或 TransportWithMessageCredential 安全性”。实际上这个异常消息不太正确,因为Tranport模式下根本就不存在UserName凭证类型。
在基于绑定的配置节中,Message模式相关选项通过<security>/<message>配置节进行设置。在该配置节中,clientCredentialType属性用于设置客户端凭证类型。在下面的配置片断中,我为寄宿的服务添加了两个采用BasicHttpBinding的终结点。其中第一个终结点的绑定为Message模式,并采用Certificate凭证。另一个终结点绑定为TransportWithMessageCredential模式,采用UserName凭证。为了保证服务寄宿的成功,我们还必须通过服务行为的形式为服务指定一个X.509证书作为服务凭证。这实际上涉及到了服务认证的话题,我们将本节后续部分进行介绍。
1: <system.serviceModel>
2: <behaviors>
3: <serviceBehaviors>
4: <behavior name="serviceCertificateBehavior">
5: <serviceCredentials>
6: <serviceCertificate findValue="Jinnan-PC" x509FindType="FindBySubjectName" />
7: </serviceCredentials>
8: </behavior>
9: </serviceBehaviors>
10: </behaviors>
11: <bindings>
12: <basicHttpBinding>
13: <binding name="messageBinding">
14: <security mode="Message">
15: <message clientCredentialType="Certificate"/>
16: </security>
17: </binding>
18: <binding name="transportWithMessageCredentialBinding">
19: <security mode="TransportWithMessageCredential">
20: <message clientCredentialType="UserName" />
21: </security>
22: </binding>
23: </basicHttpBinding>
24: </bindings>
25: <services>
26: <service behaviorConfiguration="serviceCertificateBehavior" name="Artech.WcfServices.Services.CalculatorService">
27: <endpoint address="http://Jinnan-PC/calculatorservice1" binding="basicHttpBinding"
28: bindingConfiguration="messageBinding" contract="Artech.WcfServices.Contracts.ICalculator" />
29: <endpoint address="https://Jinnan-PC/calculatorservice2" binding="basicHttpBinding"
30: bindingConfiguration="transportWithMessageCredentialBinding" contract="Artech.WcfServices.Contracts.ICalculator" />
31: </service>
32: </services>
33: </system.serviceModel>