[WCF安全系列]消息的保护等级[上篇]
到目前为止,对于WCF安全传输的三个方面,我们已经对认证进行了详细的介绍,现在我们来关注另外两个话题:消息的一致性和机密性,两者又统称为消息保护(Message Protection)。消息的安全等级指的是对整个消息或者消息的某个部分事实安全保护采用的等级。按照级别的由低到高,WCF支持如下三种不同的安全等级。在WCF的应用编程接口中,消息保护级别通过如下定义的ProtectionLevel枚举表示。
- None:不采用任何措施来保护消息的一致性和机密性;
- Sign:通过对整个消息或者消息的某个部分进行数字签名以确保消息的一致性;
- EncryptAndSign:通过对整个消息或者消息的某个部分同时进行签名和加密确保消息的一致性和机密性。
1: public enum ProtectionLevel
2: {
3: None,
4: Sign,
5: EncryptAndSign
6: }
一、消息保护级别的定义
消息的保护涉及到签名和(或者)加密,而与签名与加密相对的是签名验证和解密。要确保消息保护机制的正常进行,客户端和服务双方需要首先在保护级别上达成一致,双方按照这个约定完成属于各自的工作。从这个意义上讲,消息保护级别属于契约的一部分,所以基于消息安全级别的编程体现在契约的定义中。
我们在定义服务契约的时候,可以通过ServiceContractAttribute特性的ProtectionLevel属性为整个服务契约设置保护级别。也可以通过OperationContractAttribute特性的ProtectionLevel属性为某个具体的操作设置保护级别。ProtectionLevel属性在这两个特性中的定义如下。
1: public sealed class ServiceContractAttribute : Attribute
2: {
3: //其他成员
4: public ProtectionLevel ProtectionLevel { get; set; }
5: public bool HasProtectionLevel{ get; }
6: }
7: public sealed class OperationContractAttribute : Attribute
8: {
9: //其他成员
10: public ProtectionLevel ProtectionLevel {get; set; }
11: public bool HasProtectionLevel{ get; }
12: }
通过ServiceContractAttribute和OperationContractAttribute特性设置的消息保护级别作用在正常的功能性请求消息和回复消息中。而对于出现异常时返回给客户端的错误消息(Fault Message),我们依然需要加以保护。基于错误消息的保护级别可以通过FaultContractAttribute特性的ProtectionLevel进行设置。
1: public sealed class FaultContractAttribute : Attribute
2: {
3: //其他成员
4: public ProtectionLevel ProtectionLevel {get; set; }
5: public bool HasProtectionLevel{ get; }
6: }
上述的两种方式定义的消息保护级别都是基于整个消息的,有时候我们仅仅需要对消息中包含敏感信息的某个部分进行签名或者加密,那么就需要通过消息契约的方式定义整个消息的结构了。我们通过在实体类上直接应用MessageContractAtrribute特性来定义消息契约,而通过分别通过应用MessageHeaderAttribute和MessageBodyMemberAttribute特性将目标元素映射为消息报头成员和消息主体成员。从如下代码中可以看出,这些特性都具有一个ProtectionLevel属性。对于MessageHeaderAttribute和MessageBodyMemberAttribute特性来说,这个属性是通过从它们共同的基类MessageContractMemberAttribute继承得来。通过MessageContractAtrribute特性设置的保护级别应用于整个消息,而通过MessageContractMemberAttribute特性设置的保护级别则是基于对应的消息内容成员。
1: public sealed class MessageContractAttribute : Attribute
2: {
3: //其他成员
4: public ProtectionLevel ProtectionLevel {get; set; }
5: public bool HasProtectionLevel{ get; }
6: }
7: public abstract class MessageContractMemberAttribute : Attribute
8: {
9: //其他成员
10: public ProtectionLevel ProtectionLevel {get; set; }
11: public bool HasProtectionLevel{ get; }
12: }
13: public class MessageHeaderAttribute : MessageContractMemberAttribute
14: {
15: //省略成员
16: }
17: public class MessageBodyMemberAttribute : MessageContractMemberAttribute
18: {
19: //省略成员
20: }
二、消息保护级别的作用范围
通过上面的介绍我们知道了我们可以通过一系列基于契约(服务契约、错误契约和消息契约)的特性来定义消息的保护级别。那么,如果我们在这些特性中设置了不同的保护级别,它们之间具有怎样的优先级?WCF又采用怎样的策略来决定最终的消息保护级别呢?
定义消息保护级别的六个特性分别位于如下图所示的层次结构的四个层次中。低层次可以继承离它最近的高层次的消息保护级别。举个具体的例子,如果通过ServiceContractAttribute特性在服务契约级别将保护级别设置为Sign,该服务契约所有的操作、操作的错误契约,以及操作使用到的消息契约的默认的保护级别都变成Sign。而服务操作可以通过OperationContractAttribute特性将保护级别设置成EncryptAndSign,那么不仅仅是该操作,就连基于该操作的错误契约和消息契约对应的保护级别也动变成EncryptAndSign。
三、绑定采用怎样的消息保护级别?
上面我们着重在介绍如何在契约上定义消息的保护级别,接下来我们将关注点放在绑定上面。我们主要关注两个问题:第一、在默认的情况下绑定采用怎样的保护级别?;第二、绑定的保护级别可以自定义吗?
对于第一个问题,为了让读者有一个深刻的印象,我不直接告诉你答案,而是希望读者想我一下通过编程的方式自己去获取这个答案。在这里我们需要用到一个特殊的接口:ISecurityCapabilities。ISecurityCapabilities定义了一些简单的属性成员用以检测绑定具有怎样的安全相关的属性,其中就包括消息的保护级别。如下面的代码片断所示,ISecurityCapabilities具有两个只读属性SupportedRequestProtectionLevel和SupportedResponseProtectionLevel表示对应的绑定对于请求消息和回复消息采用怎样的保护级别。
1: public interface ISecurityCapabilities
2: {
3: //其他成员
4: ProtectionLevel SupportedRequestProtectionLevel { get; }
5: ProtectionLevel SupportedResponseProtectionLevel { get; }
6: }
那么我们现在就来检测基于某种安全模式下的绑定在默认情况下采用怎样的消息保护级别。为了使我们的程序显得简洁,我写了如下一个针对Binding类型的扩展方法PrintProtectionLevel,用于输出绑定对请求和回复消息采用的保护级别。
1: public static class BindingExtension
2: {
3: public static void PrintProtectionLevel(this Binding binding, string securityMode)
4: {
5: var bindingParameters = new BindingParameterCollection();
6: var requestProtectionLevel = binding.GetProperty<ISecurityCapabilities>(bindingParameters).SupportedRequestProtectionLevel;
7: var responseProtectionLevel = binding.GetProperty<ISecurityCapabilities>(bindingParameters).SupportedResponseProtectionLevel;
8: Console.WriteLine("{0, -25}{1, -20}{2,-20}", securityMode, requestProtectionLevel, responseProtectionLevel);
9: }
10: }
现在我们通过下面的代码检测BasicHttpBinding针对四种不同的安全级别默认采用怎样的消息保护级别。从输出结果我们可以很清楚的看到,除了TransportCredentialOnly之外,BasicHttpBinding都是采用EncryptAndSign保护级别。
1: Console.WriteLine("{0, -25}{1, -20}{2,-20}", "Security Mode","Request", "Response");
2: var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
3: binding.PrintProtectionLevel("Transport");
4:
5: binding = new BasicHttpBinding(BasicHttpSecurityMode.Message);
6: binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.Certificate;
7: binding.PrintProtectionLevel("Message");
8:
9: binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
10: binding.PrintProtectionLevel("Mixed");
11:
12: binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
13: binding.PrintProtectionLevel("TransportCredentialOnly");
输出结果:
Security Mode Request Response
Transport EncryptAndSign EncryptAndSign
Message EncryptAndSign EncryptAndSign
Mixed EncryptAndSign EncryptAndSign
TransportCredentialOnly None None
如果你将上面的测试程序用于其它的绑定(WSHttpBinding/WS2007DualHttpBinding、WSDualHttpBinding、NetTcpBinding、NetNamedPipeBinding以及Message和Both模式下的NetMsmqBinding)你会发现当安全被开启的情况下,这些绑定默认都是采用最高的消息保护级别EncryptAndSign。
但是我们编写的扩展方法不能用于Transport模式下的NetMsmqBinding。不过在表示NetMsmqBinding基于Transport安全的类型MsmqTransportSecurity中具有一个MsmqProtectionLevel属性返回采用的消息保护级别。从应用在该属性上的DefaultValueAttribute特性的定义中,我们可以直接看出NetMsmqBinding在Transport模式下默认采用的消息保护级别为Sign。
public sealed class MsmqTransportSecurity
{
//其他成员
[DefaultValue(1)]
public ProtectionLevel MsmqProtectionLevel { get; set; }
}
上面我们讨论了对于我们常用的绑定针对相应的安全模式默认采用的消息保护级别,接下来我们讨论的话题是:这些默认的保护级别可以自定义吗?答案是“部分可以”。具体来说,你只可以修改三个基于局域网的绑定针对Transport安全模式下的消息保护级别。对于NetMsmqBinding,你可以通过MsmqTransportSecurity的MsmqProtectionLevel进行设置。而用于设置NetTcpBinding和NetNamedPipeBinding基于Transport安全的TcpTransportSecurity和NamedPipeTransportSecurity类型中,都具有ProtectionLevel属性用于进行消息保护级别的显式设置。而且从应用在该属性上的DefaultValueAttribute特性中我们可以看出默认值为EncryptAndSign。你可以通过编程或者配置的方式来指定NetTcpBinding、NetNamedPipeBinding和NetMsmqBinding在Transport安全模式下的消息保护级别。
public sealed class TcpTransportSecurity
{
//其他成员
[DefaultValue(2)]
public ProtectionLevel ProtectionLevel { get; set; }
}
public sealed class NamedPipeTransportSecurity
{
//其他成员
[DefaultValue(2)]
public ProtectionLevel ProtectionLevel { get; set; }
}