这篇博文从WCF的两种安全模式谈起,讲到WCF的安全体系主要包括三个方面:传输安全(Transfer Security)、授权或者访问控制(Authorization OR Access Control)以及审核(Auditing)。而传输安全又包括两个方面:认证(Authentication)和消息保护(Message Protection)。认证帮助客户端或者服务确认对方的真实身份,而消息保护则通过签名和加密实现消息的一致性和机密性。WCF采用两种不同的机制来解决这三个涉及到传输安全的问题,我们一般将它们称为不同的安全模式,即Transport安全模式和Message安全模式。 这篇博文详细介绍了Transport ,Message 以及他们两种混合的Mixed模式,有兴趣的童鞋可以看看。
而我今天的任务就是要仔细研究下 在Transport模式和Message模式下的具体应用的异同,以便来实现上文所说的传输安全。 对于服务器,如果我们做传输安全的功能需要添加Binding的安全性设置和设置具体的服务器认证方式。如下,在配置文件中的设置:
<behavior name="metadataBehavior">
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="WCFTestService.CustomValidator, WCFTestService"/>
</serviceCredentials>
</behavior>
<binding name="basicHttpBinding" receiveTimeout="00:10:00">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic"></transport>
</security>
</binding>
Binding的安全性设置
如上的配置文件中在我们的binding的元素属性中有个security的节点,我们在这个节点设置mode的值则是设置安全方式。WCF 提供了 5 种不同的安全方式:None: 不采取任何安全措施,仅适合在内部安全环境使用。
Transport: 在传输协议级别上对通道的所有通讯进行加密,可使用的通讯协议包括 HTTPS、TCP、IPC 和 MSMQ。优点是应用广泛,多平台支持,实施方便简单,效率极高,适合高吞吐量的服务使用;缺点是只能实现点对点(point-to-point)的消息安全,在使用中介连接(Proxy)时可能会泄漏消息内容,比较适用于于 Intranet 或直接连接的环境。
Message: 通过相关标准(如 WS-Security)直接对消息进行加密来达到安全目的。优点是能实现端到端(end-to-end)的安全传输,不存在中介安全隐患,且扩展性较好。因采取工业安全标准,所以整合能力更强,适用于 Internet 服务。缺点是比 Transport 效率要低一些。
Mixed(TransportWithMessageCredential): 混合了上面两种方式。使用 Transport 方式完成消息完整性、消息机密性以及服务器认证,而使用 Message 方式完成客户端认证。Both: 使用 Transport 和 Message 共同完成所有的安全过程,比较恐怖,性能低下,只有 NetMsmqBinding 支持这一安全方式
下图列出了各种类型bind对这5种安全方式的支持:
而对于BasicHttpBinding还具有一种特殊的方式TransportCredentialOnly,它只提供针对于HTTP的客户端认证,并不能提供消息一致性和机密性的保证。(当看到这种方式我就觉得应该是最满足于我的了)。
注:在我的测试中当我设置方式为Transport在服务端出错了,日志文件显示“提供的 URI 方案“http”无效,应为“https”。”,查阅资料才知道由于所有基于HTTP的绑定都通过HTTPS来实现Transport安全,所以当选择Transport和TransportWithMessageCredential安全模式的情况下,终结点地址必须是一个HTTPS地址,而我自己的终结点地址是设置的http。
安全性解决了消息的完整性和机密性,那么剩下来的就是认证 了。WCF 支持多种认证方式,允许我们从多个 "客户端验证类型(Client Credentials Types)" 中选择适合我们需求的方案,比如经典的 "用户名/密码",或者 "Windows 集成身份认证(NTLM 或 Kerberos)"、"X.509 数字证书" 等。 接下来我们来看看客户端凭证,这也就是对应在security节点下的元素节点,分为message和transport。
基于Transport的客户端凭证
我们需要设置clientCredentialType 来确定客户端凭证的类型,它拥有六种不同的客户端用户凭证类型体现了服务端针对客户端不同的认证方式:
None:客户端无需指定用户凭证,即匿名认证。此为默认值;
Basic:采用Basic认证方式进行客户端认证。在这种认证方式下,客户端需要提供有效的用户名和密码,但是仅仅采用较弱的方式对密码进行加密。所以当且仅当你确定客户端和服务端之间的连接绝对安全的前提下,你才能用这种认证方式;
Digest:采用Digest认证方式进行客户端认证。Digest认证提供与Basic一样的认证功能,但是在安全性上有所提升。主要体现并不是直接将用户名和密码直接进行网络传输,而是对其进行哈希计算(MD5)得到一个哈希码(此过程又称为Message Digest),最终传输的是该哈希码;
Ntlm:表示使用基于NTLM方式的Windows集成认证方式对客户端进行认证;
Windows:表示使用Windows集成认证的方式对客户端进行认证。如果能够使用Kerberos,则直接采用Kerberos进行认证,否则才使用NTLM;
Certificate:表示客户端的身份通过一个X.509数字证书表示,服务端通过校验证书的方式来确定客户端的真实身份。
基于Message模式的客户端凭证
我们需要设置clientCredentialType 来确定客户端凭证的类型,它拥有两种不同的客户端用户凭证类型体现了服务端针对客户端不同的认证方式UserName和Certificate它们分别代表基于用户名/密码的凭证和针对X.509证书的凭证。在默认的情况下采用用户名/密码的凭证。
关于上述的两种客户端凭证,UserName只能用在Mixed模式下。当你选择了Message模式,则只能选择Certificate。
总结
放上两张图,第一张是系统预定义绑定类型对transport客户端凭证的支持,第二张是系统预定义绑定类型对message客户端凭证的支持
用户名/密码认证模式
在前文提到过,认证模式对于Transport和Message有稍许的不同,但都包括windows,UserName和Certificate,因为我的目的就是用UserName来解决问题,所以就只严爵了下UserName的模式。
用户名/密码凭证在客户端的设置很容易,但是我们关心的是服务端采用怎样的机制来验证这个凭证。WCF为你提供了如下三种方式来验证凭证中用户名是否和密码相符:
Windows:将用户名和密码映射为Windows帐号和密码,采用Windows认证;
MembershipProvider:利用配置的MembershipProvider验证用户名和密码;
自定义:通过继承抽象类UsernamePasswordValidator,自定义用户名/密码验证器进行验证。
我采用的是自定义,继承抽象类UsernamePasswordValidator,重写方法Validate来自定义验证规则,最后在配置文件中behavior节点的元素属性中配置
<span style="font-size:18px;"><serviceCredentials> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFTestService.CustomValidator, WCFTestService"/> </serviceCredentials></span>customUserNamePasswordValidatorType 包括两部分前面是自定义验证类的完全限定名,中间是逗号,最后是命名空间。
最后则是来完成自己的目标了,我设置security mode为TransportCredentialOnly ,然后用基于transport模式的客户端凭证,clientCredentialType设置为“Basic”,具体配置可以参见文章开头。OK 服务器大功告成。
剩下的就是客户端了,客户端来说相对要简单的多,配置文件中添加
<span style="font-size:18px;"><span style="font-family:FangSong_GB2312;"> <binding name="basicBinding"> <security mode="TransportCredentialOnly"> <transport clientCredentialType="Basic"/> </security> </binding></span></span>
客户端调用方面需要提供UserName和Password
<span style="font-size:18px;"><span style="font-family:FangSong_GB2312;">using (ChannelFactory<IService> channelFactory = new ChannelFactory<IService>("service")) { IService proxy = channelFactory.CreateChannel(); UserNamePasswordClientCredential credential = channelFactory.Credentials.UserName; credential.UserName = "Admin"; credential.Password = "123456"; </span></span>
终于写完了,也算对这一天工作的一个总结。接下来我会写一点关于服务器契约方面的问题,记录我工作当中遇到的问题,还有WCF服务器诊断。