WCF4.0进阶系列--第四章 保护企业内部的WCF服务

【摘要】

安全是任何系统至关重要的一个方面,尤其当该系统由分布式的程序和服务组成;安全还是一个非常广泛的话题。因为这些原因,你应该考虑如何在不同的场景下实现系统的安全。这些关于安全的内容将通过三章内容进行一一介绍。在本章,我们把注意力集中在企业内部WCF服务的安全管理方面。在此场景中,运行WCF服务的服务端和客户端之间已经存在必要的信任关系;通过客户端访问该服务的用户都来自同一个安全域。WCF服务可以直接访问该域内的信息,并直接使用这些信息验证用户。在第五章"保护因特网上的WCF服务",你将看到客户端程序和服务分布在不同的安全域内,并且由不安全的网络分隔开。在这种情况下,不要妄想也不可能直接验证用户。在第十七章"使用Windows CardSpace管理身份",你将看到在联合环境下如何实现一个分身元系统以帮助验证用户。  

【正文】

安全概述

安全保护运行客户端的用户、服务、及客户端与服务端之间传输的消息。安全包含一系列的议题。最熟悉和广泛的方面就是验证(证明身份)和授权(允许访问基于身份的资源)。此外,在分布式系统中,安全还包含其他许多方面,它们包括:

  • 保持服务端与客户端通信的保密性。网络中被传输的数据可能被嗅探。比如,大量的软件或硬件网络可用性分析工具,许多网络管理员使用这些工具来追踪网络中的连接问题和带宽问题,但是,恶意用户也可以使用这些工具来追踪网络中传输的包试图完成破坏的目的。这些被拦截的包可能含有财务数据、或者个人隐私信息、甚至企业内部其他的分支机构的信息。通常,你通过加密来实现保密性。
  • 预防消息被破坏或中断。即使已经可以确保信息的保密性,恶意用户仍可以在消息被传输至目的地之前翻译消息或中断消息。此时,你可以对消息实施哈希算法,然后使用其哈希值对该消息进行数字签名,最后在服务端检查客户端传输过来的消息是否被中断或篡改。
  • 确保消息传递的可验证性。即使恶意用户未能破译消息,那么他可能翻译该消息。翻译消息意味着改变消息,或不传递消息,或不停的发送消息(重放)。目前,已有多中Schemas用以检测重发问题。它们包括在消息上使用时间戳(当服务端收到消息时,如果消息的时间戳超出合理限制,那么服务端则丢弃该消息),或为消息指定一个唯一的身份(如果服务收到两个相同身份的消息,那么服务端便知道这两个消息肯定是有问题的)。类似地,使用可靠的消息协议能帮助确保消息要么在规定的时间类传输至目的地,要么在超出规定时间时向发送端发出警告。在第10章你将了解到更多细节。
  • 预防服务被模拟。在企业内部这种情况不常见。但在因特网中,一个服务可能模拟成另外一个服务以从用户那里获取保密的数据,这种现象被称之为欺骗。客户端用户程序信任与之通信的真正服务端,但客户端却将详细信息发送至另外一个与真正服务端有相似响应的,但实际上完全不同的另外一个服务端。这意味着在客户端也必须验证服务端,并且验证该服务端验证客户端身份的服务端是同一个服务端。在第五章你将通过"使用证书实现双向验证"获取更多信息。

应当记住没有绝对安全的系统。黑客和欺诈不断进化,并采取新的方式和手段去翻译,危害,甚至破坏消息流。应对危害的关键点是引入相对应的方法以减少危害的影响。幸运的是,WCF提供了可扩展的模型,该模型可以吸收新的特性并不断演化,以应对当前的安全问题,并减少新危害的发生。

Windows环境下的验证和授权

验证用户,服务必须提供一种方式供用户验证自己的身份然后证明该身份。许多组织维护一个单独的用户数据库及它们的 验证方式。在Windows环境中,这种方式就是使用活动目录。在单个组织内部,所有的服务和客户端程序都访问同一个活动目录是不可取的,因此这个数据库定义了系统的安全域。一个服务可以配置为使用活动目录数据库的信息验证用户。当用户使用应用程序访问服务时,将提示用户提供他的用户名和密码,然后将用户名和密码传输至服务。服务通过查询活动目录验证用户名和密码是否正确。

不管用户在哪个地方运行客户端应用程序,上述方式都可以不错地工作。比如,用户可以在床上使用自己的电脑执行客户端应用程序,客户端程序通过互联网连接到服务。但是,在办公室用户,已经登陆到企业的安全域后,提示他们再一次输入用户名和密码变得多余了。为什么要让用户始终登录呢? 幸运的是,Windows操作系统提供了相应的支持。如果一个用户成功登录到一个安全域,那么Windows将在用户登录过程中缓存用户凭证的详细信息。如果用户运行客户端程序去访问一个需要身份验证的服务,Windows将为该服务提供验证方面的详细信息,然后将这些信息推送至服务。该机制就是Windows集成身份验证。

当服务端验证了客户端用户的身份后,服务开始确定该用户是否被授权访问特定的操作。一般地,管理员授权用户相应的角色,服务的开发人员能识别每个角色可以访问的操作。WCF可以使用.NET Framework安全性声明去赋予角色能访问的操作,还可以使用角色程序确定一个用户所拥有的角色。.NET Framework提供了三种角色程序,你可以使用它们来存储角色的信息:

  • Windows令牌角色程序,用户基于基于活动目录组
  • ASP.NET 角色程序,用户存储在SQL Server数据库中
  • 授权存储角色程序,通过Microsoft 授权管理器工具定义用户角色,该工具允许把角色信息存储在活动目录或者XML文件中

在本章,你将使用Windows令牌角色程序。该程序是在企业内部使用Windows集成身份认证的最好方式。在第五章,你将看到如何使用ASP.NET角色程序,该程序适用于在internet上发布的服务。

传输和消息的安全

用户身份信息必须从客户端程序传输至服务。但是该信息是敏感的,因此它应该尽可能使用安全方式传输,一般情况下,你需要加密这些信息。当验证完用户,应该确定客户端和服务之间传输的消息的内容是否也需要加密,这取决于这些消息包含信息的敏感程度。可以在客户端和服务采用多种方式来完成这个目的,但是关键的是,客户端程序和服务必须协商采取统一的加密机制,并且能互相解密友对方发送过来的加密消息。使用公钥/私钥密码体系可以保证传输安全。(关于公钥体系,请参考http://technet.microsoft.com/en-us/library/aa998077(EXCHG.65).aspx)

若构建Web服务,当发送和接收消息,你可以在传输和消息这两个级别执行验证和加密。

(1)传输安全

传输级别验证发生在客户端应用程序或服务接收到消息之时,或者在知道将要接收一个消息之时在操作系统层实现。服务可以指定其所需的凭据,但是正确的凭据由操作系统负责提供和验证。

许多通信协议协可以加密和解密其接收和发送的数据。最常见的例子比如HTTPS,它使用安全套接字层(SSL)技术,通过证书所提供的密钥来加密和解密数据。当客户端使用HTTPS协议连接到该Web服务时,基础传输架构协调客户端和服务端执行加密的程度。然后两者交换含有密钥的证书,该密钥用于双方加密和解密数据。因为这些都发生在传输层,对客服端和服务都是透明的;所以你需要做的仅仅是指定HTTPS为其通信传输协议。当然,管理员必须为服务的宿程序安装和配置适当的证书。

注意:在本章,你将在一个自我寄宿WCF服务的程序上配置HTTPS;如果你在IIS中寄宿WCF服务,配置HTTPS的过程将有所不同,具体信息将在第五章中介绍。

你还可以在基于TCP协议上实现传输安全;Windows操作系统在TCP上实现了传输安全(TLS)。TLS是SSL的继承者,并提供与SSL相似的功能。但是,与SSL使用HTTP绑定不一样,在WCF下,TCP绑定自动使用TLS。

命名管道也支持传输安全,但其支持消息安全。

(2)消息安全

服务负责消息级别的验证。用户凭证包含在消息中,并发送至服务端,服务端必须验证这些凭证是有效的。此外,服务端和客户端都负责消息保密性和一致性。它们使用两者认可的加密算法和一组协商后的密钥对消息进行加密和解密。WS-Security规范描述了消息层的安全结构,该规范在许多Web服务上已经实现。遵循WS-Security推荐,你能确保服务和客户端的互操作,因此你应该使用WS-Security规定的技术而不是WCF。

传输安全比消息安全有优势,因为传输安全可以基于硬件支持并且这种支持非常高效。加密和解密数据是一个与资源相关的过程,所以任何能改进性能的方法都是非常值得尝试的。此外,传输级别的验证检查在客户端程序实际发送应用层的消息前是强制执行的,因此此时执行验证能以较少的网络流量快速识别验证成功亦或失败。

传输安全最主要的优点在于其基于点对点执行操作。当服务收到消息时,该消息已经被基础传输机制解码。如果一个服务只是简单地传递消息至另外一个服务,而不是处理该消息。那么该中转服务有足够的权限读取转发消息的内容,这意味着该中间服务在传递消息之前能修改消息或提取消息的保密内容。此时,使用消息安全能够消除该安全问题。

消息安全提供点对点加密。客户端程序和服务分别做为终点目的地,两者同意对消息使用相同的加密密钥和加密算法。当消息达到中转服务时,消息仍旧是加密了的。如果中间服务无权读取密钥或者不知道加密的算法,那么它就不能轻易地解密消息。

实施消息安全看起来似乎需要对服务做很多开发工作。实际上,WCF简化了很多事请,并且减少了服务端点上标准绑定与这些绑定对应的代码的开发工作。要实现消息层安全, 你所需要做是选择合适的绑定。

在Windows域内实现安全

在下面的练习中,你将了解到在单个企业中的一般场景下如何实现传输安全和消息安全。由于按照顺序来介绍这些概念,大家比较容易的理解。所以,首先你将会了解到如何通过加密消息以实现消息的保密性;然后,你将了解到如何在Windows环境下实现用户验证;最后,你将了解到如何使用Windows口令角色程序来授权对服务的访问。

在消息层级别保护基于TCP协议的服务

NetTcpBinding绑定自动在传输级别使用TLS加密数据。NetTcpBinding绑定还支持在消息层加密,让你可以进一步控制你使用的算法。下面的例子展示了如使用消息层安全实现加密消息。

在使用NetTcpBinding绑定的服务上启动消息加密

1. 复制第三章的ProductsServiceFault解决方案到文件夹Chapter4,然后打开该方案,并重命名为ProductsService。

2. 使用WCF配置工具打开ProductsServcieHost下的app.config文件:

3. 创建一个新的使用NetTcpBinding的绑定。

4. 设置该绑定的名字为"ProductsServiceTcpBindingConfig",然后切换到"安全"标签下。

5. 设置安全模式为"Message",加密采用为"Basic128"。该设置将使绑定使用消息层安全;用户将提示输入用户名和密码,所有的消息将使用128位算法的高级加密标准(AES)来加密。该算法被广泛采用,是因为其执行速度相对很快,而且在企业内部为消息提供了足够的保密性。但是,当你在互联网上发送消息时,请使用256位的算法(默认算法)。

注意:如果你先择安全模式为"None",那么该绑定将不会加密数据,其他传输层和消息层的相关设置将被忽略。如果选择"Transport"模式,那么绑定将采用传输层安全而非消息层安全。如果选择"TransportWithMessageCredential"模式,那么在消息层实现用户身份验证,在传输层执行加密。传输层加密比消息曾加密更有效,尽管传输层加密需要管理员做更多的设置,在本章后面内容你将了解到这些设置的细节内容。

6. 选择该服务的端点,然后选择"NetTcp_IProductsService",设置该端点的绑定设置的值为"ProductsServiceTcpBindingConfig"

7. 保存配置文件。

8.在Visual Studio开发app.config,你将看到该文件如下面所示:

此时,该服务将要求客户端使用相同的消息层全设置,那么我们将在下一步配置客户端。 【关于凭据类型更多消息,请参考http://msdn.microsoft.com/zh-cn/library/ms733836.aspx

在使用NetTcpBinding绑定的客户端上启动消息加密

1.使用WCF配置工具 打开ProductsClient项目下的app.config

2. 找到绑定下的NetTcpBinding_IProductsServcie,然后修改安全模式为"Message",加密算法使用"Basic128"

3. 保存并退出。

4. 修改program.cs,使其使用NetTcpBinding与服务ProductsService进行通信

program.cs
ProductsServcieClient proxy = new ProductsServcieClient("NetTcpBinding_IProductsServcie");
Console.WriteLine(
"Test 1: List all products");
string[] procutNumbers = proxy.ListProducts();
foreach (string productNumber in procutNumbers)
{
Console.WriteLine(
"Number: {0}", productNumber);
}

Console.WriteLine(
"\nBinding address:{0}", proxy.Endpoint.Address);

配置WCF服务跟踪消息

1. 使用WCF配置工具打开ProductsServiceHost下的app.config文件,然后设置该服务的诊断信息:

LogEntireMessage属性指定追踪是否包括接收和发送消息的body部分;如果为true,那么将包含body部分。如果为false(默认值),那么将只包含Header部分。
LogMessageAtServiceLevel如果设置为true,那么当消息到达服务和离开服务时开始追踪消息。如果你使用消息安全模式;在消息级别,当收到消息时,追踪将显示解密前的消息;当发送消息时,追踪将显示加密前的消息。
LogMessageAtTransportLevel如果设置为true,在传输级别,接收和发送消息的消息将被追踪。如果你使用消息安全,在传输级别收发的消息是加密后的消息。如果你使用传输层安全,那么在接收消息时,将显示解密前的消息;在发送消息是,追踪将显示加密前的信息。  

2. 选择诊断下的追踪源,然后点击右键—创建新的追踪源。

WCF所有的追踪信息来自一个或多个追踪源。在本例中,你将使用WCF提供的MessageLogging源,其追踪消息。你还可以指定其他追踪源。比如ServcieModel源去追踪所有发生在服务上的事件(服务开始监听请求事件,接收请求事件,发送响应事件)。你甚至还可以实现你自定义的追踪源,尽管它超出本书讨论的范围。

3. 选择"System.ServiceModel.MessageLogging"为追踪源。并设置追踪级别为"Verbose"

4. 选择诊断下的侦听器,然后右键,创建新的侦听器:

5. 设置侦听器的名字为"MessageLog",设置初始数据的存放地址

6. 设置TraceOutputOptions为None,设置方法为点击下拉箭头,然后情况所有复选框。

7. 确保TypeName为System.Diagnostics.XmlWriterTraceListener

8. 把在第三部设置的追踪源添加到该侦听器中来,操作方法:点击侦听器右下角的添加按钮,然后在添加追踪源对话框中选择"System.ServcieModel.MessageLogging",在点击确定按钮。

9. 保存app.config,然后退出WCF配置工具

运行WCF服务和客户端,并检查追踪输出结果

1. 启动ProdctsServiceHost,点击开始按钮,启动服务

2. 启动ProductsClient

3. 退出ProductsClient,停止ProductsService服务,然后退出ProductsServcieHost。

4. 启动Visual Studio自带的服务追踪查看器(Service Trace Viewer)  

5. 打开服务追踪查看器之后,选择文件—打开,打开之前存放追踪的文件

6. 在服务追踪查看器中左边面板中,却换到"消息"标签,列出了ProductsService服务收发的消息,它们根据其动作加以区分。 在这些消息的前半部分,处于http://schemas.xmlsoap.org/ws/2005/02/trust/命名空间下。它们关注发送和审核用户身份,协商客户端和服务发送和接收消息时使用的加密机制和加密密钥。这些消息的后半部分,是WCF服务收发的消息,它们使用http://tempuri.prg命名空间区别。

7. 点击第一个名字为http://tempuri.prg/IProductsService/ListProducts的动作,你可以发现每个动作发生了两次。这是因为你追踪了每个消息两次:一次发生在消息级别,另外一次发生在传输级别。

8. 点击右下部分的"消息"标签,对应的窗口将显示整个SOAP消息。该消息是由传输级别发送至消息级别。该消息header的内容相当长,如果你不忙,你可以查看其内容。有趣的是处于该消息尾部的body内容。其内容是来自客户端的加密后的ListProducts请求。元素<e:ClipherValue>包含了该请求的数据,在下图使用黄色强调了该部分:

9. 从左边面板,点击第二个http://tempuri.prg/IProductsService/ListProducts,然后点击右边的对应的"消息"标签,该消息是解密后的请求,在黄色选区内,你可以看到解密后的请求内容。

10. 你从左边面板中选择第一个http://tempuri.prg/IProductsService/ListProductsResponse,你可以看到未加密的消息,该消息包含了ListProdusts的响应;该消息是由消息层级别送至传输级别,因此该消息还没有被加密

11. 在左边面板,点击第二个http://tempuri.prg/IProductsService/ListProductsResponse, 你可以看到消息已经加密。该消息将由传输级别发送至客户端。

12. 关闭服务追踪查看器。

在传输级别保护基于HTTP协议的服务

你应该记得,ProductsServiceHost程序对外暴露了两个端点允许客户端连接:一个基于TCP协议,另外一个基于HTTP协议。基于HTTP协议的端点配置为使用BasicHttpBinding绑定。BasicHttpBinding绑定遵守WS-basic profile 1.1规范,其目的是为了使用传统的Web服务和客户端。 该协议完全能与ASP.NET Web服务互操作。默认情况下,该绑定提供最小的安全性;比如,它不支持消息基本加密或者验证。为了实现消息保密并保留与ASP.NET互操作,你应该使用传输安全。这要求你将该服务配置为使用HTTPS协议。这就是我们下面练习将做的事情。

为使用BasicHttpBing绑定的服务指定传输安全

1. 在Visual Studio,使用WCF配置管理工具,打开ProductsServiceHost项目下的app.config;并在WCF配置管理工具的左边面板中,点击Binding,然后点击右边的"创建一个新的绑定配置"

2. 设置名称为ProductServcieBasicHttpBindingConfig;

3.切换到"安全"标签,设置模式为"Transport"。

4. 转到端点下的BasicHttpBinding_IProductService,确认Address为https://localhost:8000/ProductsService/Service.svc; 并选择绑定配置为"ProductServcieBasicHttpBindingConfig"

5. 保存配置文件,退出WCF服务配置管理工具,然后编译该项目,确认没有警告和错误。

为使用BasicHttpBinding绑定的客户端指定传输安全

1. 使用WCF配置管理工具,打开ProductsClient项目下的app.config;并在WCF配置管理工具的左边面板中,展开绑定文件夹,然后点击右边的"创建一个新的绑定配置"

2. 设置名称为ProductClientBasicHttpBindingConfig;

3. 切换到"安全"标签,设置模式为"Transport"。

4. 转到端点下的BasicHttpBinding_IProductService,确认Address为https://localhost:8000/ProductsService/Service.svc; 并选择绑定配置为"ProductClientBasicHttpBindingConfig"

5. 保存配置文件,退出WCF服务配置管理工具

6. 修改productsClient下的program.cs文件,使其使用BasicHttpBinding绑定与服务ProductsServcie通信:

program.cs
static void Main(string[] args)

{

try

{

ProductsServcieClient proxy
= new ProductsServcieClient("BasicHttpBinding_IProductsService");



}



}

7. 编译该项目,确认没有警告和错误。

此时,尽管你已经更新了ProductsServiceHost和ProductsClient,但是你还没有配置传输安全所使用HTTPS协议。如果你试图运行服务,你将会收到错误。在接下来的练习中,我们将为WCF服务创建证书,并且使用netsh工具为该服务配置SSL。

配置WCF HTTP端点使用SSL证书

1. 用管理员身份运行Visual Studio Command Prompt.

2. 执行下列命令以创建一个用于测试的证书:

该命令创建的证书存储在本地计算机帐户的Personal证书下。关于makecert命令的详细使用情况,请参阅:http://msdn.microsoft.com/en-us/library/bfsktky3.aspx.

为了使用netsh实用工具,你需要道证书的thumbprint值,该值是一个16进制的字符串,这个值是唯一的,用来区别各个证书。你可以通过管理控制台(Microsoft Management Console)来获取该值

3. 在Visual Studio Command Prompt中输入mmc, 启动管理控制台。然后点击File—Add/Remove Snap-in。

4. 选择"证书",然后在出现的对话框中选择"本机用户",然后点击"下一步"按钮。

5. 选择本地计算机,然后点击"完成"按钮

6. 确认证书已经出现在"Add or remove snap-ins"的右边面板中,然后点击"确认"按钮

7. 点击管理控制台左边面板的Personal,然后选择"证书",你可以发现生成的证书已经出现在右边面板。双击该证书,以查看该证书的详细信息。

8. 切换到该证书的"详细"标签页,拖动中间的滚动条至底部,你可以发现"Thumbprint"属性,复制该属性的值。(每台计算机该值不一样),然后关闭证书窗口。

9. 回到Visual Studio Command Prompt, 执行下列命令。注意参数ipport的值为0.0.0.0:8000,其中0.0.0.0表示本机的ip地址。certhash的值为Thumbprint的值去掉控制空格后所剩字符串。appid可以选择任意一个GUID。【注:我是执行了一个SQL语句生成的该GUID(select newid());此外vs也自带了生产GUID的工具】。

10. 添加8000端口为https的保留端口:参数user的值为你当前的Windows用户名

为WCF客户端添加代码以覆盖默认的证书有效性检查

1. 在Visual Studio中打开ProductsClient项目下的program.cs文件。

2. 添加如下的两个引用

using System.Security.Cryptography.X509Certificates;
using System.Net;

3. 添加下面的类到program.cs

PermissionCertificatePolicy
class PermissionCertificatePolicy
{
string subjectName;
static PermissionCertificatePolicy currentPolicy;

PermissionCertificatePolicy(
string subjectName)
{
this.subjectName = subjectName;
ServicePointManager.ServerCertificateValidationCallback
+=
new System.Net.Security.RemoteCertificateValidationCallback(RemoveCertValidate);
}

public static void Enact(string subjectName)
{
currentPolicy
= new PermissionCertificatePolicy(subjectName);
}

bool RemoveCertValidate(object sender, X509Certificate cert,
X509Chain chain, System.Net.Security.SslPolicyErrors error)
{
if (currentPolicy.subjectName == subjectName)
return true;
return false;
}
}

4. 修改program.cs的main方法

program.cs
static void Main(string[] args)
{
try
{
PermissionCertificatePolicy.Enact(
"CN=HTTPS-Server");

ProductsServcieClient proxy
= new ProductsServcieClient("BasicHttpBinding_IProductsService");

}

}

运行WCF服务和客户端

1. 启动ProdctsServiceHost,点击开始按钮,启动服务

2. 启动ProductsClient

3. 你将得到如下结果:我用红色矩形框强调了服务端和客户端所使用端点的地址

 在消息级别保护基于HTTP协议的服务

你可以配置BasicHttpBinding绑定的安全模式为消息安全,从而为服务提供消息安全。在该模式下,服务使用SOAP消息安全加密消息。服务必须已经安装了证书,客户端用户使用服务证书的公钥执行加密。在消息开始传输消息之前,服务发送包含公钥的证书;或者在使用客户端程序前,管理员在客户端安装服务证书。在第五章,你将将了解到更多细节。此外,在该模式下,WCF服务能支持的验证机制要求客户端使用一个证书识别自己,即在该模式下,你不能使用Windows集成身份验证。

该绑定的安全模式中,有一个选项是"TransportWithMessageCredential"。该模式结合传输安全和消息安全。 服务使用HTTPS协议和证书在传输级别上为消息提供一致性和保密性。对客户端的授权在消息级别使用SOAP消息安全来处理,客户端程序能提供一个用户名和密码来验证用户。你在第五章将了解到更多细节。

如果你想为WCF服务实现消息安全,并且使用较少的工作量和配置文件。那么你可采用WS2007HttpBinding绑定。WS2007HttpBinding绑定由WS-*规范组成,并且遵守WS-Security默认地加密消息和用户验证规范。下面的例子展示了如何使用WS2007HttpBinding来实现基于HTTP协议的消息层安全。

配置WCF服务使用WS2007HttpBinding的端点

1. 打开ProductsService解决方案,使用WCF服务配置工具打开ProductsServcieHost项目中的app.config。在服务下的端点处,点击右键"新建一个服务端点"

2. 设置该端点的名字为"WS2007HttpBinding_IProductsService",地址为"http://localhost:8010/ProductsServcie/Service.svc",绑定选择"WS2007HttpBinding",服务契约为"Products.IProducts"

3. 保存设置,然后退出WCF服务配置管理工具

4. 生成ProductServiceHost项目,确保没有错误警告

5. 使用管理员身份运行"Visual Studio Command Prompt",然后执行下列命令并确保命令成功执行。

配置WCF客户端使用WS2007HttpBinding绑定连接到服务。

1. 打开ProductsService解决方案,使用WCF服务配置工具打开ProductsClient项目中的app.config。在服务下的端点处,点击右键"新建一个端点"

2. 设置该端点的名字为"WS2007HttpBinding_IProductsService",地址为"http://localhost:8010/ProductsServcie/Service.svc",绑定选择"WS2007HttpBinding",服务契约为"ProductsClient.ProductsService.IProductsServcie"

3. 保存设置,然后退出WCF服务配置管理工具

4. 修改program.cs文件,是proxy使用WS2007HttpBinding与服务通信。

ProductsServcieClient proxy = new ProductsServcieClient("WS2007HttpBinding_IProductsService");

 5. 重新生成ProductClient项目,确保没有错误警告

运行服务和客户端程序,并检查追踪结果。

1. 在资源管理器中,转到路径*\Step.by.Step\Chapter4,删除ProductsService.svclog

2. 启动ProductsServcieHost,然后点击"开始"启动ProductsServcie服务;然后启动ProductsClient。

3. 退出ProductsClient。 停止ProductsService,并退出ProductsServcieHost程序。

4. 启动Service Trace Viewer,打开*\Step.by.Step\Chapter4\ProductsService.svclog。在Service Trace Viewer的左边面板中,点击"消息"标签。

5. 点击第一个消息"http://tempuri.org/IProductsService/ListProducts",然后在Service Trace Viewer右边面板的右下部分,点击"消息标签",然后滚动到该消息内容的底部。你可以看到该消息已经加密了。

与使用BasicHttpBinding或NetTcpBinding不同,当你使用WS2007HttpBinding时,不必指定任何加密设置。因为你在创建WS2007HttpBinding时,该绑定自动在消息级别加密数据。

6. 在Service Trace Viewer左边面板,选择第二个消息"http://tempuri.org/IProductsService/ListProducts",在右边面板的右下部分,点击"消息标签",然后滚动到该消息内容的底部,你可以看到由消息级别传输至服务的为加密的消息:

7. 查看ListProductsResponse消息,你可以看到未加密的消息从服务传输至消息级别;该消息在消息级别加密后传输至传输级别。(下面图中,第一张图显示了从服务回传给消息级别的未加密的消息;第二章显示了从消息级别传输至传输级别的加密后的消息)

  

8. 关闭Service Trace Viewer。

WS2007HttpBinding绑定默认使用256的AES加密算法加密数据。你可以在创建绑定行为时通过指定AlgorithmSuite属性的值来选择不同的算法。这和前面指定NetTcpBinding绑定的加密算法是一样的操作过程。

验证Windows用户

到目前为止,你已经了解到如何配置NetTcpBinding,BasicHttpBinding和WS2007HttpBinding实现对消息的加密,从而实现消息的保密性和隐私性。但是,只有当服务能验证客户端用户的身份时,安全地传输消息才是有意义的。在下面的练习中,你将看到当服务和客户端在同一个Windows域中时,服务如何验证客户端用户。在第五章,你将了解到如何在不同的安全域中,服务如何执行验证。

首先,你要为ProductsService服务添加代码,以显示调用ProductsService中ListProducts操作的用户名。然后,你将在激活验证功能后的WCF服务上看到从客户端传输至服务端的用户身份。

显示地调用WCF服务操作的用户名

1. 开打ProductsServcie解决方案中,为ProductsServiceLibrary项目添加PresentationFramework, PresentationCore, System.Xaml, 和 WindowsBase引用。

2. 打开ProductsServcie.cs文件。

3. 添加下面两行语句到该文件的头部。

using System.Threading;
using System.Windows;

4. 找到ListProducts方法,在该方法的开始部分,添加如下语句

ListProducts
public List<string> ListProducts()
{
string userName = Thread.CurrentPrincipal.Identity.Name;
MessageBox.Show(
string.Format("Username is {0}", userName),
"ProductsService Authentication", MessageBoxButton.OK);

}

第一行用户获取执行当前线程的Windows的用户。第二行代码用于显示一个消息提示框。

5. 修改ProductsClient项目中program.cs文件。是客户端使用BasicHttpBinding与服务进行通信

ProductsServcieClient proxy = new ProductsServcieClient("BasicHttpBinding_IProductsService");

6. 重新生成解决方案,然后先运行ProductsServiceHost并点击开始按钮启动ProductsService。然后再启动ProductClient。

7. 当你的客户端代码运行后,将出现一个消息提示框。该消息提示框显示运行客户端程序的用户名。从消息提示框中我们可以看到该用户名为空,这不是一个错误。因为在默认情况下,BasicHttpBinding绑定不传输任何用户身份信息。所有的消息都是以匿名传递的。所以如果你想验证用户,该绑定目前的设置是没用的。

8. 当你点击OK后,你可以看到客户端程序继续运行,并得到对应的结果:

9. 退出ProductsClient;然后停止ProductsServcie并退出ProductsServiceHost。

在下面的练习中,我们将为BasicHttpBinding实现用户验证。对于该绑定和其他WCF内置的绑定有许多验证的选项以供选择。

配置BasicHttpBinding在WCF服务端实现身份验证。

1. 使用WCF服务配置工具打开ProductsServiceHost项目的app.config文件。

2. 在左边面板中,展开banding文件夹,然后选择ProductsServiceBasicHttpBindingConfig节点。

3. 在右边面板中,切换到"安全"标签。此时你可以注意到TransportClientCredentialType属性的值为None,这表明服务并不要求客户端提供用以验证的用户信息,因此可以连接到该服务的任何人都可以发送消息并调用服务的操作。

4. 设置TransportClientCredentialType执行的值为Basic。当使用Basic时,客户端程序必须提供用户名和密码,并将它们传输至服务。WCF运行时使用该消息来验证客户端程序,如果用户是合法的,将为该服务的用户提供一个合法身份。

5. 保存设置,并关闭WCF服务配置工具。

6. 运行PrductsServcieHost,启动ProductsServcie服务;然后启动ProductsClient。

7. 此时,客户端将抛出异常"MessageSecurityException was unhandled"

8. 关闭ProductsClient;停止ProductsService服务,并退出ProductsServiceHost程序。

修改WCF客户端使其为WCF服务提供用户凭证

1. 使用WCF服务配置工具打开ProductsClient项目的app.config文件。

2. 在左边面板中,展开绑定文件夹,然后选择ProductsServiceBasicHttpBindingConfig节点。

3. 在右边面板中,切换到"安全"标签。

4. 设置TransportClientCredentialType执行的值为Basic

5. 保存设置,并关闭WCF服务配置工具

6. 开发program.cs,做下列的修改

program.cs
static void Main(string[] args)
{
try
{
PermissionCertificatePolicy.Enact(
"CN=HTTPS-Server");

ProductsServcieClient proxy
= new ProductsServcieClient("BasicHttpBinding_IProductsService");
proxy.ClientCredentials.UserName.UserName
= "XX\\ABCD"; // (计算机名或域名\\用户名)
proxy.ClientCredentials.UserName.Password = "****"; // (用户密码)

}

}

7. 运行PrductsServcieHost,启动ProductsServcie服务;然后启动ProductsClient。你将会看到消息提示框显示了当前运行客户端的用户名。

点击ok之后,服务的ListProducts方法将执行。其结果和之前的实验一样。

8. 关闭ProductsClient;停止ProductsService服务,并退出ProductsServiceHost程序。

使用Basic认证,你可以提供用户的用户名和密码,运行服务的WCF运行时将检查这些凭证是否有效。如果你提供了无效的用户密码,WCF运行时将拒绝客户端的请求,并返回MessageSecurityException异常,该异常的消息为"HTTP请求被禁止…"

当客户端用户没有登录到服务所在的安全域时,基本验证是一个不错的解决方案。但是,如果客户端已经登录到该域中后,你可以使用Windows集成身份验证自动提供用户凭证,而不需要再一次提示用户登录。

配置BasicHttpBinding绑定使WCF服务和客户端使用Windows集成身份验证

1. 使用WCF服务配置工具编辑ProductsServiceHost的app.config

2. 在左边面板中,展开"绑定"文件夹,选中ProductsServcieBasicHttpBindingConfig节点

3. 在右边面板中,点击"安全"标签

4. 设置TransportClientCredentailType属性的值为Windows。

5. 保存配置文件,并关闭WCF服务配置工具

6. 使用WCF服务配置工具编辑ProductsClient的app.config

7. 重复上述的2至5步骤。设置ProductsServcieBasicHttpBindingConfig节点下TransportClientCredentailType属性的值为Windows

8. 保存配置文件,并关闭WCF服务配置工具

9. 编辑ProductsClient项目下的program.cs文件

10. 注释掉之前实验添加的两行:

// proxy.ClientCredentials.UserName.UserName = " XX\\ABCD ";// (计算机名或域名\\用户名)
// proxy.ClientCredentials.UserName.Password = "*****";// (用户密码)

11. 保存文件,并重新生成项目。

12. 打开ProductsServiceHost,并点击开始按钮运行ProductsService服务;然后启动ProductsClient。

消息提示框将出现,并且显示由ProductsClient发送至服务的Windows用户名。此时,并未要求你提供用户名和密码,因为WCF客户端程序的WCF运行时自动从用户进程中收集这些信息。

注意:如果你没有做第10步,即没有在代码中注释掉用户名和密码,上面的程序仍然可以运行,只不过你提供的用户名和密码被自动忽略掉。但是,当你想使用另外一个Windows用户,你可以通过ClientCredentials属性,你可以设置该属性domain,username和password值,并提供给服务。当你设置这三个中的任何一个,那么将覆盖WCF运行时从已经登录的用户进程中收集的相应的值。

13. 点击OK按钮,确认程序能正确运行。

14. 退出ProductsClient;点击"停止"按钮停止ProductsService,并推出ProductsServcieHost。

当你使用Windows集成身份验证,用户名和密码将不会以明文传输。你可以在NetTcpBinding和WS2007HTTPBinding绑定的消息层使用Windows集成身份验证,而不要求在传输层实现加密。

检查使用NetTcpBinding绑定的验证机制

1. 使用WCF配置工具打开ProductsServiceHost的app.config文件

2. 在左边面板中,展开绑定文件夹,选中ProductsServcieTcpBindingConfig节点

3. 在右边面板中,点击"安全"标签

4. 核实MessageClientCredentialType属性的值是Windows

5. 关闭WCF配置工具

6. 编辑ProductsClient下的program.cs文件,使其使用NetTcpBinding_IProductsServcie端点与服务通信

7. 打开ProductsServcieHost,点击"start"启动ProductsService服务;然后打开ProductsClient

8. 与上一个练习同样的结果将会出现。

9. 关闭ProductClient程序;点击"stop"停止ProductsService服务,并退出ProductsServcieHost。

授权用户

服务识别用户的身份后,将确定该用户能否执行所请求的操作。一些操作可能比其他的操作要求更多的权限。比如在ProductsServcie中,你可能希望允许任何库管员查询AdventuresWorks数据的产品信息,但是限制他们访问ChangeStockLevel操作(存货总监可以运行该操作)。WCF的开发人员可以使用.NET Framework的特性去指定哪个用户或角色有权访问其所请求的操作。你可以通过指定特性或者编程方式去实现用户授权。

WCF实现的授权机制要求读取一个定义用户和角色的数据库。如果你使用活动目录授权,那么WCF将从活动目录数据获取用户及角色。其第一步是配置WCF服务,使其使用Windows 口令角色程序从活动目录中获取角色信息。

配置WCF服务使用Windows口令角色程序(Token Role Provider)

1. 使用WCF配置工具打开ProductsServiceHost下的app.config

2. 在左边面板中,展开"高级"文件夹,展开"服务行为"文件夹,然后点击empty name节点

3. 在行为面板上,点击添加按钮

4. 在添加行为元素扩展选择对话框中,选择"serviceAuthorization",然后点击添加按钮

5. 在WCF配置管理器的左边面板中,选择serviceAuthorization元素,并确认右边"General"标签下的"PrincipalPermissionMode"属性的值为"UseWindowsGroups"。

6. 保存配置文件,并退出WCF配置管理工具

接下来的第二个步骤是定义能请求WCF服务操作的角色。当使用Windows 口令角色程序时,活动目录组就是对应的角色,因此你在活动目录中定义组并添加用户到改组。在下面的练习中,将添加Fred和Bert两个用户到对应的组中。

为Warehouse和Stock Controller部门员工创建组

1. 点击Windows开始菜单,在我的电脑上点击右键,然后点击管理。进入计算机管理控制台

2. 在计算机管理控制台中,展开左边面板系统工具—本地用户和组,选择组,然后在组上点击右键,并点击创建一个新组

2. 在出现的创建组对话框中,输入组名"WarehouseStaff",然后点击创建按钮。然后再输入"StockControllers"再点击创建。然后点击关闭按钮,关闭创建新组对话框。

3. 在计算机管理控制台中,展开左边面板系统工具—本地用户和组,选择用户,然后在点击右键,并点击创建一个新用户。然后输入用户名Fred,密码Pa$$w0rd;点击创建按钮完成该用户的创建。再继续输入"Bert"和"Pa$$w0rd"创建用户Bert

4. 用户创建完毕后,这两个用户将出现在计算机管理控制台右边面板中,选择Bert,然后点击右键,并点击属性;

5. 在用户Bert属性对话框中,切换到"Members of"标签。点击"添加"按钮,将用户组"WarehouseStaff"添加进来。

6. 按照第5步骤的做法,为用户Fred添加WarehouseStaff组和StockController组

7. 关闭计算机管理控制台。

现在你可以使用定义好的用户组去指定能访问ProductsService服务操作的角色。为了展示声明方式和编程方式指定用户授权,你将使用特性来指定查询AdventureWorks数据库操作的角色;通过代码来实现修改数据库操作的角色。

指定WCF服务操作的角色

1. 在Visual Studio中打开ProductsServiceLibrary项目下的ProductsServcie.cs文件。

2. 添加下列using语句

using System.Security;
using System.Security.Permissions;
using System.Security.Principal;

3.为ListProducts, GetProduct和CurrentStockLevel方法指定下列特性

ProductsService
[PrincipalPermission(SecurityAction.Demand, Role="WarehouseStaff")]
public List<string> ListProducts()
{

}

[PrincipalPermission(SecurityAction.Demand, Role
="WarehouseStaff")]
public ProductData GetProduct(string productNumber)
{

}

[PrincipalPermission(SecurityAction.Demand, Role
="WarehouseStaff")]
public int CurrentStockLevel(string productNumber)
{

}

4. 通过代码方式指定能访问ChangeStockLevel操作的角色

ChangStockLevel
public bool ChangStockLevel(string productNumber, short newStockLevel, string shelf, int bin)
{
WindowsPrincipal user
= new WindowsPrincipal(
(WindowsIdentity)Thread.CurrentPrincipal.Identity);
if (!(user.IsInRole("StockControllers")))
{
throw new SecurityException("Access denied");
}


}

5. 修改ProductsClient的program.cs文件,使其能捕获安全异常

program.cs
static void Main(string[] args)
{
try
{

proxy.ClientCredentials.Windows.ClientCredential.Domain
= "****-PC";
proxy.ClientCredentials.Windows.ClientCredential.UserName
= "Bert";
proxy.ClientCredentials.Windows.ClientCredential.Password
= "Pa$$w0rd";

}

catch (FaultException ex)
{
Console.WriteLine(
"{0}: {1}", ex.Code.Name, ex.Reason);
}

catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

Console.ReadLine();
}

测试WCF服务的授权

1. 生成ProductsServcie解决方案

2. 打开ProductsServiceHost,点击"开始"启动ProductsServcie服务

3. 启动ProductsClient。你将会得到如下结果

4. 退出ProductsClient。

5. 修改ProductsClient的program.cs文件,使其使用Fred身份去读取ProductsService

program.cs
static void Main(string[] args)
{
try
{

proxy.ClientCredentials.Windows.ClientCredential.Domain
= "****-PC";
proxy.ClientCredentials.Windows.ClientCredential.UserName
= "Fred";
proxy.ClientCredentials.Windows.ClientCredential.Password
= "Pa$$w0rd";

}


Console.ReadLine();
}

6. 再次运行ProductsServiceHost,点击"start"启动ProductsServcie;运行ProductsClient。你将得到如下结果

 7. 退出ProductsClient;停止ProductsServcie并退出ProductsServcieHost

模仿客户端用户身份去访问资源

WCF验证用户后确认了访问用户的身份,然后服务执行权限检查以确认用户允许执行所请求的操作。实现该操作的方法可能会访问宿主所在计算机的资源。在默认情况下,服务将试图使用自己的用户凭证去获取这些资源。比如,当ProductsServcie服务的一个方法链接到AdventureWorks数据库时,将使用运行该服务的账户去连接数据库。当使用Windows集成身份验证时,可以指定WCF服务使用验证后的身份去访问资源。如果使用Bert连接到服务,那么WCF服务根据Bert的访问权限访问数据库内对应的资源。该原则同样适合于访问其他资源,比如,文件,文件夹和共享的网络资源。使用模仿,使管理员能更好的控制WCF服务读取和写入敏感的信息;同时还能提供一定程度的安全――因为可以连接到WCF服务的用户,不一定能执行获取或修改机密数据,除非管理员明确地允许用户可以访问这些数据。

你可以在服务的操作上启用模仿功能,通过指定OperationBehavior特性的Impersonation属性:

[PrincipalPermission(SecurityAction.Demand, Role = "WarehouseStaff")]
[OperationBehavior(Impersonation=ImpersonationOption.Required)]
 public List<string> ListProducts()
{

}

  Impersonation值的详细介绍

属性值

描述

ImpersonationOption.Required

需要进行模拟

ImpersonationOption.Allowed

如果凭据可用并且ImpersonateCallerForAllOperations等于true,将执行模拟

ImpersonationOption.NotAllowed

不执行模拟,如果ImpersonateCallerForAllOperations等于true,则服务启动时将会发生验证异常

 

WCF提供服务行为元素ServiceAuthorizationElement,有一个属性是ImpersonateCallerForAllOperations。如果其值为true,服务中的所有操作都模拟调用方。如果操作指明ImpersonationOption.NotAllowed,那么模拟调用将失败。你可以在服务配置文件中,设定该属性的值:  

app.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
...
<system.serviceModel>
...
<behaviors>
<serviceBehaviors>
<behavior>
<serviceAuthorization principalPermissionMode="UseWindowsGroups"
impersonateCallerForAllOperations
="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>

 

你通过在客户端设置AllowImpersonationLevel的值来指定客户端点的行为,从而定义服务将使用的安全模拟级别。下面的代码片段展示了如果指定AllowImpersonationLevel   

app.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="ImpersonationBehavior">
<clientCredentials>
<windows allowedImpersonationLevel="Impersonation" />
...
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
...

<client>
...
<endpoint
address="http://localhost:8010/ProductsService/ProductsService.svc" behaviorConfiguration="ImpersonationBehavior"
binding
="ws2007HttpBinding"
contract
="ProductsClient.ProductsService.IProductsService"
name
="WS2007HttpBinding_IProductsService" />
</client>
</system.serviceModel>
</configuration>

 

下表列出了AllowImpersonationLevel可能的值及其说明

属性值

描述

None

未指定模拟级

Anonymous

服务器进程无法获取有关客户端的标识信息,且无法模拟客户端

Identification

服务器进程可以获取有关客户端的信息(如安全标识符和特权),但是无法模拟客户端。这对于导出自己的对象的服务器(如导出表和视图的数据库产品)十分有用。在不能使用其他正使用客户端安全上下文的服务的情况下,服务器可以使用检索到的客户端安全信息做出访问验证决策

Impersonation

服务器进程可以在其本地系统上模拟客户端的安全上下文。服务器无法在远程系统上模拟客户端

Delegation

服务器进程可以在远程系统上模拟客户端的安全上下文。

总结

在本章,你了解到如何使用三种常见的WCF绑定来为WCF服务提供不同程度的保护。你还了解到如何在服务级别和消息级别配置客服端和服务端的消息加密。你还了解到如何指定绑定的验证模式及如何将客户端的Windows凭据传递到WCF服务。你还了解到如何为已经验证的用户提供对服务操作的授权访问,及如何通过模仿功能为验证身份后的用户提供相应的资源访问权限。

现在,你应该关注不同绑定对应的不同安全设置,其默认的设置是否符合特定的环境。比如,如果你在企业内部部署WCF服务,那么你可以使用NetTcpBinding或者NetNamePipeBinding绑定,并且实现传输安全。但是,当你允许企业外部也可以访问该服务时,你应使用NetTcpBinding或者基于HTTP的协议(比如BasicHttpBinding或WS2007HttpBinding),并且根据服务的需求和维护现存服务端和客户端的需求,实现传输层安全或者消息层安全。如果你构建的服务,需遵守Basic Profile 1.1, 你应该使用BasicHttpBinding绑定,并且使用基于传输层安全,如果需要验证用户,则使用基本验证。如果你构建的服务遵循WS-Security规范,那么你应该使用WS2007HttpBinding,并且配置其使用消息层安全。

本章源代码下载

【参考】

http://msdn.microsoft.com/en-us/library/ff650794.aspx Improving Web Services Security: Scenarios and Implementation Guidance for WCF

http://msdn.microsoft.com/en-us/library/ms788756.aspx WCF security architecture

posted @ 2011-05-24 10:03  On the road....  阅读(4238)  评论(11编辑  收藏  举报