适用于:
Web Services Enhancements 2.0 for Microsoft® .NET
Microsoft® .NET Framework 1.0 或更高版本
Microsoft® Windows Server 2003 或带有 Service Pack 1 的 Windows XP
Kerberos Security Tokens
摘要: 学习 Web Services Enhancements 2.0 中对策略、信任、内容令牌和新编程模式等功能的支持。(本文包含一些指向英文站点的链接)
单击下载源代码,可以从 Microsoft Download Center 下载 Rock Paper Scissors 应用程序的源代码。
Microsoft 发行了 Web Services Enhancements (WSE) 1.0 版,以在支持的产品中启用安全、路由和附件,同时它还支持高级 Web 服务。Web Services Enhancements 2.0(英文)已从简单支持基本协议发展到支持核心功能与操作系统的集成,并增强了策略、信任和上下文令牌功能。
WSE 也是对 .NET Framework 支持的扩展,它用于创建和使用 Web 服务,而 WSE 2.0 采用新的编程模式。过去,Web 服务支持一直依赖 Internet Information Server (IIS) 作为其 HTTP 服务器主机;现在,WSE 2.0 支持通过 TCP/IP 或在进程内来发送消息。这样,您可以通过对等、单向、异步等方式将消息从服务器发送到客户端。
下面,我们将简要介绍 WSE 2.0 的几个主要功能,并假设您已熟悉 WSE 1.0。有关 WSE 1.0 的详细信息,请参阅 Programming with Web Services Enhancements 1.0 for Microsoft .NET(英文)。
为了展示 WSE 2.0 的各个方面,我想创建一个应用程序以展示一些新安全功能和消息处理功能。具体来说,我喜欢使用 TCP 消息处理支持来显示对等通信,并使它在 Windows 安全性领域工作。我想到的是一个简单的分布式版本的旧 Rock Paper Scissors 游戏,现在可以用 WSE 2.0 进行安全分发。
Rock Paper Scissors 是一个两人儿童游戏,传统上包括让每个孩子拍手三次,在第三次拍手时出示石头、剪刀或布的形状。根据下表中所示理由决定胜者:
石头 | 布 | 剪刀 | |
---|---|---|---|
石头 | 平局 | 布裹石头。 布方赢! |
石头砸坏剪刀。 石头方赢! |
布 | 布裹石头。 布方赢! |
平局 | 剪刀剪开布。 剪刀方赢! |
剪刀 | 石头砸坏剪刀。 石头方赢! |
剪刀剪开布。 剪刀方赢! |
平局 |
该游戏通常用于确定由谁先选择参加沙地棒球赛的选手、谁吃最后的冰淇淋,甚至由谁击打对方的手臂。
我们将 Rock Paper Scissors 搬出沙地,直接应用在具有 WSE 2.0 的安全、协作、团结的企业技术中。该游戏的消息处理结构如下面的图 1 所示。
图 1:Rock Paper Scissors 消息处理和安全模型
Rock Paper Scissors 有两个主要组件:一个是名为 RPSService 的 ASP.NET Web 服务,另一个是由多个用户运行的对等应用程序。RPSService 的作用是让用户注册玩游戏或寻找对手来玩游戏。对等应用程序先是与 RPSService 通信以找到要与之通信的对方,然后在确定对手后直接与其对方进通信。
对等:对于消息处理,WSE 2.0 从基于传统 HTTP 请求/响应(即 RPC 环境)转换为采用可能同时具有对等消息处理、异步消息处理和消息队列的环境。
Kerberos:整个应用程序是通过使用集成的 Windows Kerberos 支持创建的。使用 WSE 2.0,您可以用一种安全的方式连接网络。我们将进行验证、数字签名和加密消息以确保具有最高的安全性,且这将完全基于 Windows 用户帐户。
便捷管理:不用编写代码来控制访问,我们便可以通过策略配置文件来配置消息的安全性。通过策略文件,可以控制要实现的安全种类、要对消息的哪部分进行数字签名或加密及其实现方式,还可基于消息时限性控制接受条件。目前,可以控制安全性的相应人员为:您的管理员。
Rock Paper Scissors:该应用程序的重要作用不在于应用程序本身,而是它与本例中不同实体通信时使用安全消息、策略和寻址的方式。您猜不到,我正想象我的老板说:“为了决定职员今年的奖金,我想让你们每人运行一个小的应用程序 RockPaperScissors.exe...”
我们要讨论的 WSE 2.0 的第一部分是支持 Kerberos 安全令牌。WSE 1.0 支持用户名令牌和 X.509 安全令牌。可以将这些令牌添加到安全令牌的消息集合,并用于创建数字签名或执行加密。对于 WSE 2.0 来说,当在 Windows Server 2003 或带有 Service Pack 1 的 Windows XP 上运行时,则已添加了 Kerberos 令牌支持。更重要的是,Kerberos 令牌支持能够与集成的 Windows 安全性一起使用,这样,不再需要将用户名映射成 Windows 用户,也不用另外设置用户数据库,可以基于 Windows 用户控制对 Web 服务的访问。
在为 RPSService 添加代码之前,我要做的第一件事是在自己的项目中添加对 Microsoft.Web.Services 程序库的引用。如果您的计算机上安装的是 WSE 1.0,那么添加引用时注意选择 2.0 版的程序库很重要。幸运的是,程序集的版本号就列在程序集名称之后,因此可以轻松地选择正确的程序集。图 2 显示的是选定了 WSE 2.0 程序库的 Add Reference(添加引用)对话框。请注意,1.0 版本的程序库就在选定的程序库之前。
图 2:将引用添加到 WSE 2.0 程序集
下面的代码演示如何通过编程的方式将 Kerberos 令牌添加到令牌消息集合。它是一方私下向另一方发送其动作时所使用的代码。该代码使用 WSE 2.0 的异步 TCP 消息功能(随后将详细说明),但用于添加令牌和加密的代码类似于 WSE 1.0 中用于其他类型令牌的代码。
using Microsoft.Web.Services.Security.Kerberos; a€| KerberosToken peerToken; a€| peerToken = new KerberosToken("host/" + OpponentUri.Host); a€| envelope.Context.Security.Tokens.Add(peerToken); envelope.Context.Security.Elements.Add(new EncryptedData(peerToken));
请注意,令牌是通过传递连接主机的名称创建的。用于创建该令牌的 Kerberos 标签允许当前用户与指示的主机进行通信。具体来说,该令牌将用于加密要发送的消息,以便只有该远程主机才可以读取这些消息。
与其他类型令牌不同,Kerberos 令牌使用当前的 Windows 用户安全上下文创建该令牌。如果您查询已创建的令牌,您将会找到一个 Principal 成员属性,它指示创建该令牌的用户。我们从接收消息(带有 Kerberos 令牌)的代码的主体中获取用户名。下面的 opponent 变量是一个 KerberosToken 对象,该对象是从传入消息的 Tokens 集合中获得的。可以像下面获取名称那样来获取关于令牌创建者的信息,但也可以调用 IsInRole() 方法来通过编程的方式确定 Active Directory 组成员。
this.opposingNameLabel.Text = "Playing: " + opponent.Principal.Identity.Name;
注意:如果在 Windows XP 上运行 Web 服务,则使用 Kerberos 令牌试图连接到 Web 服务时,您可能会遇到以下错误:
Microsoft.Web.Services.Security.SecurityFault: An invalid security token was provided ---> System.Security.SecurityException: Unable to validate incoming Kerberos ST. LsaLogonUser failed with the following message: A required privilege is not held by the client. Substatus is 0.
出现该错误是由于调用名为 LogonUser 的安全 API 时,ASPNET 帐户无法验证 Kerberos 令牌。调用 LogonUser API 的用户帐号要求具有“作为操作系统的一部分来操作”特权。在默认情况下,ASPNET 帐户(即运行 ASP.NET 代码的帐户)不具有该特权。建议您在合适的服务器平台如 Windows Server 2003 上运行已设置 Kerberos 安全的 Web 服务。在 Windows Server 2003 上,调用 LogonUser 时不需要“作为操作系统的一部分来操作”特权。在 Windows XP 上,可以使用 Local Security Policy 管理应用程序来配置拥有“作为操作系统的一部分来操作”特权的帐户,包括 ASPNET 帐户,但应该注意这会造成 ASP.NET 应用程序的安全性不够严密。
虽然以上讨论的代码与 WSE 1.0 中的代码非常相似,但它具有一个优点:将 Principal 对象与一个令牌关联,可以用来查找某个具体安全令牌的特定属性。这种方法存在的问题在于仍要编写代码来确定应属于管理任务的访问功能。为此,WSE 2.0 提供了创建策略文件支持。
策略文件基于 WS-Policy 规范,该规范是为允许 Web 服务声明传入消息有关安全性等方面的要求而创建的。如果希望签名或者加密所有或部分消息,WSE 2.0 允许我们控制 Web 服务的用户在消息中使用的安全令牌类别,以及控制对接收消息时限的限制,甚至可以为传入的消息指定角色成员限制。
以下策略文件说明 Web 服务接收的消息应使用 Kerberos 令牌对消息主体进行签名。
<?xml version="1.0" encoding="utf-8"?> <policyDocument xmlns="http://schemas.microsoft.com/wse/2003/06/Policy"> <mappings xmlns:wse="http://schemas.microsoft.com/wse/2003/06/Policy"> <mapDefault policy="#policy-5903e02b-9c11-4dc5-8ca0-42d4e9d0bcde" /> </mappings> <policies xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"> <wsp:Policy wsu:Id="policy-5903e02b-9c11-4dc5-8ca0-42d4e9d0bcde" xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"> <wsse:Integrity wsp:Usage="wsp:Required" xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/12/secext"> <wsse:TokenInfo> <SecurityToken xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext"> <wsse:TokenType>wsse:Kerberosv5ST</wsse:TokenType> </SecurityToken> </wsse:TokenInfo> <wsse:MessageParts Dialect="http://schemas.xmlsoap.org/2002/12/wsse#part"> wsp:Body() </wsse:MessageParts> </wsse:Integrity> </wsp:Policy> </policies> </policyDocument>
policyDocument 根元素有两个子元素:mappings 和 policies。policies 元素含有一个或多个表示特定要求集的 Policy 元素。在示例中,策略包含一个 Integrity 要求,表示该消息需要数字签名。TokenInfo 元素包含关于所需令牌种类的信息,本示例中为 Kerberos 令牌。最后,这一特定完整性要求指出必须签名的文档部分为正文,这是使用 MessageParts 元素指示的。
policyDocument 的 mappings 部分仅将特定终结点与 policies 部分中的策略关联。本示例不包括特定终结点的任何映射,仅包括单一的默认映射。
除了实际创建策略文件以外,还需要让 WSE 程序库知道该策略的存在。因此,有必要配置 Web 服务以使用创建的策略文件。为此,需要修改 Web.config 文件。下面的 Web.config 文件给出了为启用 WSE SoapExtension、启用 WSE 配置部分中的处理程序以及添加接收策略缓存而作的更改。
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <!-- 添加对配置部分的引用。 请注意, 类型名称要换行以便于阅读,并且不应包含 换行符。--> <section name="microsoft.web.services" type="Microsoft.Web.Services.Configuration .WebServicesConfiguration, Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </configSections> <system.web> <!-- 为了简洁,删除其他元素 --> <webServices> <soapExtensionTypes> <!-- 添加 WSE SoapExtension。请注意, 类型名称要换行以便于阅读并且 不应包含换行符。--> <add type="Microsoft.Web.Services .WebServicesExtension, Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" /> </soapExtensionTypes> </webServices> </system.web> <!-- WSE Configuration Section --> <microsoft.web.services> <policy> <receive> <cache name="policyCache.xml" /> </receive> </policy> </microsoft.web.services> </configuration>
至此,已将 Web 服务配置成只接受符合 policyCache.xml 文件中策略要求的请求。
如果需要让管理员为特定的 Web 服务配置策略,我们同样希望对使用 Web 服务的应用程序进行类似配置。总而言之,如果管理员更改策略后我们必须重新生成使用的应用程序,则并没有完全实现配置消息要求时无需重新生成 Web 服务的功能。
要为使用的应用程序添加该功能来发送符合特定策略的消息,需要本地具有该程序可以使用的策略文件。对于要调用 .asmx Web 服务的 Rock Paper Scissors 对等应用程序,我们只是将 policyCache.xml 文件复制到该可执行程序的工作目录。如同对 Web 服务的处理,将该应用程序配置成使用其 .config 文件中的策略文件(本示例中为 RockPaperScissors.exe.config)。该配置文件如下所示。
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <!-- 添加对配置部分的引用。请注意, 类型名称要换行以便于阅读,并且不应包含 换行符。--> <section name="microsoft.web.services" type="Microsoft.Web.Services. Configuration.WebServicesConfiguration, Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </configSections> <microsoft.web.services> <policy> <send> <cache name="policyCache.xml" /> </send> </policy> </microsoft.web.services> </configuration>
对等应用程序将通过发送符合 policyCache.xml 文件中的要求的消息与 Web 服务进行通信。
WSE 1.0 与 WSE 2.0 之间的区别之一是对 WS-Addressing 的支持。WS-Addressing 替换了 WSE 1.0 中支持的 WS-Routing 规范的大部分功能。从功能方面来说,WS-Addressing 不是将重点放在路由路径上,而是提供一种机制将 To 和 From 标头添加到 SOAP 信封中。WS-Addressing 也支持 Action、ReplyTo 和 FaultTo 标头。Action 标头类似于通过 HTTP 发送 SOAP 消息时通常使用的 SOAPAction HTTP 标头。对于 .asmx Web 服务,HTTP SOAPAction 标头用于确定接收传入消息时应调用服务的哪种 Web 方法。与此类似,Action SOAP 标头用于确定通过非 HTTP 传输接收消息时要调用的函数。
在 Rock Paper Scissors 应用程序中,我们还将利用 ReplyTo 标头来确定接收下一消息的地址。当对等应用程序之一向 RPSService 发送单向 RegisterPlayer 消息时,将指定一个 ReplyTo 终结点,指示对等通信开始时用来接收对方发送的消息的地址。与此类似,当第二个对等应用程序将 FindPlayer 消息发送到 RPSServer 时,RPSServer 将返回一个带有 ReplyTo 标头的消息,该标头指示第一个对等应用程序的终结点。这就告诉第二个对等应用程序应该将自己的下一消息发送到第一个对等应用程序的终结点。在余下的对等消息中,继续指定 ReplyTo 标头来不断指示接收下一消息的地址。
下面是对等应用程序使用的部分代码,在代码中,首先侦听对等通信,然后在调用 RPSServer RegisterPlayer Web 方法之前通过 ReplyTo 标头指示 URI。
myPeerUri = new Uri("soap.tcp://" + System.Net.Dns.GetHostName() + ":3131/RPSPeer1"); SoapReceivers.Add(myPeerUri, typeof(PeerService)); RPSServ.RPSServerWse proxy = new RPSServ.RPSServerWse(); proxy.RequestSoapContext.ReplyTo = myPeerUri;
在 WSE 2.0 中,消息处理是新增功能的主要部分。WSE 2.0 通过异步 TCP 或请求/响应方式 TCP 为进程中的通信提供支持。在本示例的 Rock Paper Scissors 应用程序中,我们将发送消息来指示玩家对于特定游戏实例是否选择了石头、布或剪刀。由于我们依靠用户交互来确定发送消息的内容,我们不可能无限地等待需要发送响应的请求,因此,我们使用异步 TCP 消息来实现特定游戏的通信。一个人发送其动作后应用程序耐心的等待,直到对方玩家发送其动作。您可以将这看作类似于向仓库发送发货单,必须等待,直到有人手动包装材料,才能发送一个指示请求完成的响应。
在上面显示的 ReplyTo 代码中,给出了建立 TCP 终结点需要的部分代码。通过 SoapReceiver 类中的 Add 方法,可以注册侦听代码。在本示例中,我们创建了一个名为 PeerService 的类,它是侦听传入的 Rock Paper Scissors 游戏的 SoapReceiver 类的子类。PeerService 类只是重载了处理传入消息的 Receive 方法。该类的代码如下所示:
public class PeerService : SoapReceiver { public static Form1 Form; protected override void Receive(SoapEnvelope envelope) { Form.opponentPlay = (char)envelope.GetBodyObject(typeof(char)); foreach (SecurityToken tok in envelope.Context.Security.Tokens) { if (tok is KerberosToken) { Form.opponent = (KerberosToken)tok; break; } } Form.OpponentUri = envelope.Context.ReplyTo; if (Form.peerToken == null) Form.peerToken = new KerberosToken("host/" + Form.OpponentUri.Host); Form.opposingNameLabel.Invoke( new Form1.ReceivePlayDelegate(Form.ReceivePlay)); } }
Receive 方法采用 SoapEnvelope 对象作为参数。SoapEnvelope 类是从 XmlDocument 类派生得到的,这样,您可以通过标准的 XML DOM 接口来访问 SOAP 正文和标头。我们没有使用 DOM 接口,而是利用 GetBodyObject 方法基于 SOAP 消息正文中的 XML 来创建一个类,该方法在功能上使用了 XmlSerializer。我们将动作信息保存在 Form 类的一个公有属性中。
我们还从请求中获取 Kerberos 令牌并将它保存。它将用于以后获取对手的名称。然后,保存对手的 ReplyTo URI,以便我们了解接收响应的地址。我们亦可基于 URI 中显示的主机先行建立 Kerberos 令牌,它将用于加密返回的消息。
SoapReceivers 支持将侦听传入的连接,并在收到消息时调用指示的类的 Receive 方法。进程的线程池中的某个线程将调用 Receive 方法,大多数情况下,该线程不是处理应用程序主窗口消息泵的线程。因此,在 Receive 方法的末尾,我通过 Windows 窗体中的一个控件调用 Invoke 方法。这就启动了主窗口线程中指定的委托功能,以使窗体中各种控件的常规交互正确进行。在本示例中,委托确定两次动作后的赢家并相应地更新用户界面。
当调用 SoapReceivers.Add 方法(下面再次显示了该方法)时,我们传递两个参数:一个 URI 和侦听类的类别类型。URI 指示了多项内容。首先,URI 类型为 soap.tcp。这表示它是一个通过 TCP 发送的 SOAP 消息的 URI。URI 的主机名称指示要侦听的计算机,其后是用来侦听传入连接的 TCP 端口号。在本示例中,我们使用端口 3131。最好不要使用 1000 以下的端口号,因为它们是为特定类型的应用程序而保留的(例如,端口 80 用于 HTTP 服务器)。
myPeerUri = new Uri("soap.tcp://" + System.Net.Dns.GetHostName() + ":3131/RPSPeer1"); SoapReceivers.Add(myPeerUri, typeof(PeerService));
向异步 TCP 侦听器发送消息也同样简单。我们只要创建一个 SoapEnvelope 对象,并使用 SetBodyObject 方法将对象序列化到正文的 XML 中。SoapEnvelope 包含一个 Context 属性,该属性的使用类似于常规 HTTP 绑定的 SOAP 交互时使用的 SoapRequestContext 和 SoapResponseContext 属性。我们用它来建立需要的 Action SOAP 标头和可选的 ReplyTo 标头。我们还要添加前面创建的 Kerberos 令牌并用它来加密消息。对于发送消息,我们使用 SoapSender 类,该类与我们用于侦听传入消息的 SoapReceiver 类相对应。SoapSender 类获取其构造函数中的终结点 URI,然后将传递的 SoapEnvelope 发送到 Send 方法。发送对等消息的代码如下所示。
// Send Message SoapEnvelope envelope = new SoapEnvelope(); envelope.SetBodyObject(myPlay); envelope.Context.Action = new Action(OpponentUri.ToString()); envelope.Context.Security.Tokens.Add(peerToken); envelope.Context.Security.Elements.Add( new EncryptedData(peerToken)); envelope.Context.ReplyTo = myPeerUri; SoapSender peerProxy = new SoapSender(this.OpponentUri); peerProxy.Send(envelope);
您可以从 Microsoft Download Center 获取 Rock Paper Scissors 应用程序的完整源代码(英文)。
通过 Rock Paper Scissors 应用程序,我们研究了 WSE 2.0 的许多功能,但是还有许多其他功能。用户名令牌还可以与 Windows 安全性集成,如 Kerberos 令牌。由于支持安全上下文令牌,您可以建立有效的对称密钥,用于对两个终结点之间的多个消息进行加密,而无需为每个消息生成一个新的密钥。除了安全上下文令牌以外,还支持创建安全性令牌服务 (Security Token Service),该服务为两个终结点之间的通信颁发上下文令牌。
对于消息处理,我们仅讨论了对发送异步 TCP 消息的支持,其实对同步请求/响应的支持也是同样出色。该支持使用 SoapMethod 属性,类似用于 .asmx Web 服务的 WebMethod 属性,而且操作也相似。此外还支持在单独的应用程序空间中调用服务,这似乎不是很好,但 Windows 消息泵(创建窗式应用程序的基础)同样基于在单独的应用程序空间中发送消息。
WSE 2.0 更为吸引人的地方之一是具有许多可扩展点。您可以做任何事情,包括从创建自己的自定义令牌处理程序到添加自己的策略支持。像 WSE 1.0 一样,您仍然可以扩大处理范围,但更重要的是如何在更高的层次上前进并利用已有的工作。
在 MSDN 中,将会有更多的文章深入探讨 WSE 2.0。我们将深入地讨论策略、安全性、消息处理和其他内容。希望 Rock Paper Scissors 为您展示了使用 WSE 2.0 开发的高级 Web 服务支持所具有的一些奇特新功能。同时希望它能像引发我们一样引发您的想象力,在全新的环境中开发 Web 服务应用程序。