WCF4.0进阶系列—第十章 实现可靠的会话

【前言】

许多时候,创建WCF客户端应用程序和服务时,它们被部署在不同的计算机上,并通过网络通信。在网络中会遇到很多问题,如果不考虑安全问题,那么网络的主要问题是网络的不可靠性。有线网络和无线网络传输的信息都非常容易被拦截、干扰、甚至发生用户掉线的情况。很显然,这些都是不可接受的。

如果WCF服务运行在PerSession服务实例模式下,那么客户端和WCF服务之间的会话将包含多条消息。在因特网中,发送方向目标发送消息时,不同的消息使用不同的路由,因此很可能消息达到目的地的顺序与它们被发送的顺序不一致。此时,对于WCF服务而言,应按照客户端发送的顺序而不是按照接收到的顺序处理消息。因此,客户端程序和服务需要实现指明消息顺序的协议。

通过公开网络传输的消息非常容易受到攻击。它们可能面临拦截、中断、改变等一系列令人讨厌的攻击。一些Web服务规范(WS-*)用于保护消息,并且在前面的章节中你也已经了解到WCF如何实现其中的一些规范。另外一个常见的安全问题是"重放攻击":第三方拦截消息并不停地推送这些消息至接收方。用于可靠地发送消息并消除重放攻击的相关规范是WS-ReliableMessaging规范。

严格地讲,可靠的消息传递和可靠的会话是两个不同但相关的概念。可靠的消息传送关注点在于确保消息仅仅传送一次,而可靠的会话提供发送和接收一系列可靠消息的环境。在WCF中,可靠的会话独立于可靠的消息传送;使用可靠的消息传送,你可以在客户端和服务之间提供点到点的可靠会话。本章将同事调查这两个方面;你将学习如何使用WCF提供可靠的会话和可靠的消息传送,以及学习如何配置重放识别。

【正文】

使用可靠的消息传送

为了处理丢失消息或消息按照错误的顺序达到服务的问题,OASIS组织提出了WS-ReliableMessaging规范[1]。该规范定义了一个可相互操作的协议,它以一种可靠的方式在单个发送方和单个接收方之间传送消息。WCF为该协议提供了一种实现:试图确保所有从发送方发送的消息将不会重复地达到目的地。该实现同样尝试识别丢失的消息,并尽可能地重新发送丢失的消息。最坏的情况是,如果一个消息不可避免的丢失了,WCF运行时将抛出一个异常。这意味着如果一个消息丢失,客户端程序或者服务,或者两者,将关注该问题并采取相应的补救行为。

WCF选择性地支持顺序化消息,以确保消息在接收方除按照它们被发送的顺序处理。使用该协议,消息可能按照不同的顺序达到接收方,但是WCF架构可以缓存这些消息并以正确的顺序把这些消息呈现给服务。

依照WS-ReliableMessaging规范所规定的可靠消息传送并不能表明任何形式的消息持久化或者消息队列,理解这一点非常重要。 该协议要求发送者发送消息和接收者接收消息在同一时刻运行。如果不能接受消息,或接收方没有运行,或网络失败,发送方将接收到一个错误消息。用另外一句话说即:当使用可靠的消息传送时,WCF运行时将保证如果可以传送消息则传递消息,否则它将警告发送者它不能发送消息——WCF将不会静悄悄地丢失消息。

注意:通过使用事务和消息持续性,而不是WS-ReliableMessaging协议,消息队列以自己的形式实现可靠的消息传送。你将在第十二章 "实现单向和异步操作"中了解到使用消息队列为WCF传送消息。

在WCF中实现可靠的会话

在WCF服务中配置可靠的消息传送非常简单。WS-ReliableMessaging协议生成一系列额外的消息供客户端和服务端的WCF运行时使用,以协调两者的行为。启用WCF服务的追踪功能可以帮助你更好的理解WS-ReliableMessaging协议是如何工作的。

在ShoppingCartService服务和客户端程序中实现可靠地消息传送

1. 使用Visual Studio打开*\WCF\Step.by.Step\Chapter10\ShoppingCartService方案。

2. 在解决方案浏览器中,使用服务配置编辑工具打开ShoppingCartServiceHost项目的app.config文件

3. 在配置面板,展开绑定,然后选择ShoppingCartServiceNetTcpBindingCfg端点配置。在右边的面板中,移动滚动条至ReliableSession区域,然后设置Enabled属性的值为true。确认Ordered属性的值为true;InactinvityTimeout属性的值为10分钟(00:10:00)。

服务的WCF运行时使用Ordered属性确定是否按照客户端发送消息的顺序把消息传递给服务;该属性为可选属性但它对可靠地消息传送非常重要。WCF运行时将在InactivityTimeout属性指定的时期内等待,以确定是否发生了错误或者消息丢失。如果发生超时, WCF运行时发送一个"序列终止"的SOAP错误消息至客户端程序(如果客户端程序不再运行或者通信失败,那么该错误将不会被接收),然后终止会话;如果服务使用了事务,回滚事务所做的更改。

因为你将检查由WS-ReliableMessaging协议生成的消息,所以在接下来的步骤中,你需要配置服务追踪。

4. 在控制面板中,点击诊断节点;在诊断面板中,点击EnableMessageLogging。

5. 在控制面板中,展开诊断节点,然后点击MessageLogging节点。在消息日志面板中,设置LogEntireMessage属性为true,并设置LogMalformedMessages属性为false。

6. 在控制面板中,展开侦听节点,然后点击ServiceModelMessageLoggineListener节点。在右边面板中,更改InitData的路径属性。设置该属性的值为*\WCF\Step.by.Step\Chapter10\ShoppingCartService\app_messages.svclog。

7. 保存配置文件,并退出服务配置编辑工具。

8. 客户端的绑定设置必须和服务端点的属性相匹配:使用服务配置编辑工具编辑ShoppingCartClient项目的app.config文件。在绑定节点下将显示ShoppingCartServiceNetTcpBindingCfg绑定的属性,在ReliableSession区域下设置enable为true,并确认Ordered属性为true,InactinvityTimeout属性为10分钟(00:10:00)

如果在InactinvityTimeout指定的时间内,未接收到任何消息,那么客户端程序的可靠消息传送将引起超时并抛出异常。然而,客户端程序一般情况下只能接收请求对应的响应消息(在第十六章 "使用回调合约发布和订阅事件"中,你将了解到客户端程序也可能在其他的时间接收消息)。客户端程序可能在网络中休眠但仍然保持活动状态,即使它不向服务发送消息(比如:可能忙于显示数据,或者收集用户收入)。

类似地,在InactinvityTimeout指定的时间内,如果服务的WCF运行时未接收到任何消息,那么WCF服务将发生超时。为了避免这种情况的发生,如果客户端一段时间内未向服务发送任何消息,那么客户端的WCF运行时定期地向服务发送一个"保持连接"消息。发生这个时点一般为InactinvityTimeout指定时间的一半。"保持连接"消息实际上有双向目的:不仅使服务知道客户端程序仍然处于运行状态,同时表明服务可以访问。客户端的WCF运行时期望服务端的WCF运行时回应一个回执(表示服务已经收到客户端发送的消息);如果在InactinvityTimeout指定的时间内未收到回执, 客户端的WCF运行时则认为服务已经停止,并生成一个客户端可以处理的"序列终止"SOAP错误消息。

9. 保存配置文件,并退出服务配置编辑工具。

检查由客户端程序生产的追踪消息

1. 在非调试模式下运行解决方案。在ShoppingCartClient控制台窗口中将显示"Press ENTER when the service has started",然后按下ENTER键

客户端程序像之前一样执行,并显示购物车包含的两个水瓶和1个山地自行车座位组件,随后显示商品已经购买消息。按ENTER键关闭客户端程序窗口。在宿主程序控制台窗口中,按ENTER键停止并关闭服务。

2. 启动服务追踪查看器(在Windows开始菜单à程序àMicrosoft Visual StudioàMicrosoft Windows SDK toolsàService Trace Viewer)

3. 在服务追踪查看器中,打开*\WCF\Step.by.Step\Chapter10\ShoppingCartService文件夹下的app_messages.scvlog文件

4. 在左边面板,点击消息标签,然后点击第一条消息。在右下面板,点击消息标签。检查该消息的内容;它应该如下所示(每个人电脑上看到的MessageId和Identifier的值可能不一样)

<MessageLogTraceRecord>

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">

<s:Header>

<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/rm/CreateSequence</a:Action>

<a:MessageID>urn:uuid:aecb9876-b56c-48bd-885a-569e0d84e9ae</a:MessageID>

<a:To s:mustUnderstand="1">net.tcp://localhost:8080/ShoppingCartService</a:To>

</s:Header>

<s:Body>

<CreateSequence xmlns="http://schemas.xmlsoap.org/ws/2005/02/rm">

<AcksTo>

<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>

</AcksTo>

<Offer>

<Identifier>urn:uuid:9736fb3e-e23d-4dd5-8d3b-d2cfab1e50a3</Identifier>

</Offer>

</CreateSequence>

</s:Body>

</s:Envelope>

</MessageLogTraceRecord>

WS-ReliableMessaging协议在客户端与服务的会话中通过为这些消息设置顺序号作为唯一标志符的方式组织消息。该协议中的第一条消息是CreateSequence消息,它由客户端的WCF运行时发送。该消息初始化可靠的会话,其body部分包含一个由WCF运行时生成的唯一标识符,服务在响应客户端时也将使用该标识符。

5. 在左边面板中,点击第二条消息,然后检查该消息的内容,它应如下所示

<MessageLogTraceRecord>

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">

<s:Header>

<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/rm/CreateSequenceResponse</a:Action>

<a:RelatesTo>urn:uuid:aecb9876-b56c-48bd-885a-569e0d84e9ae</a:RelatesTo>

<a:To s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/anonymous</a:To>

</s:Header>

<s:Body>

<CreateSequenceResponse xmlns="http://schemas.xmlsoap.org/ws/2005/02/rm">

<Identifier>urn:uuid:60e12f4b-d0ff-4301-a2aa-617a37361808</Identifier>

<Accept>

<AcksTo>

<a:Address>net.tcp://localhost:8080/ShoppingCartService</a:Address>

</AcksTo>

</Accept>

</CreateSequenceResponse>

</s:Body>

</s:Envelope>

</MessageLogTraceRecord>

该消息是CreateSequenceResponse消息,由服务端的WCF运行时回传至客户端。该消息header中的RelatedsTo指明了消息ID与由客户端WCF运行时生成的,并与客户端发送至服务的CreateSequence消息中的标识符相同。(因此,客户端的WCF运行时将知道如何关联CreateSequence消息和服务回传的响应消息;客户端还可能同时发送多个可靠的会话)。注意该消息的boday部分包含一个新的标识符。 客户端的WCF运行时在后续向服务发送消息时,必须提供该标识符。

6. 检查第三条消息,它应如下所示

<MessageLogTraceRecord>

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://schemas.xmlsoap.org/ws/2005/02/rm" xmlns:a="http://www.w3.org/2005/08/addressing">

<s:Header>

<r:AckRequested>

<r:Identifier>urn:uuid:60e12f4b-d0ff-4301-a2aa-617a37361808</r:Identifier>

</r:AckRequested>

<r:Sequence s:mustUnderstand="1">

<r:Identifier>urn:uuid:60e12f4b-d0ff-4301-a2aa-617a37361808</r:Identifier>

<r:MessageNumber>1</r:MessageNumber>

</r:Sequence>

<a:Action s:mustUnderstand="1">http://adventure-works.com/2011/06/08/ShoppingCartService/AddItemToCart</a:Action>

<a:MessageID>urn:uuid:312c55fa-6e04-4e0f-ba1f-241bfe205a2d</a:MessageID>

<a:ReplyTo>

<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>

</a:ReplyTo>

<OleTxTransaction s:mustUnderstand="1" b:Expires="58880" xmlns="http://schemas.microsoft.com/ws/2006/02/tx/oletx" xmlns:b="http://schemas.xmlsoap.org/ws/2004/10/wscoor">

<PropagationToken>AQAAAAMAAADg5D/APEHEQZIe2jz6NSCUAAABAAAAAAB4AAAAALa1bQAAAABgIWdtPHlobV8AAQAEDwkAAAAAgAAAAADs5ScAJSFnADE4YzNmMTQwLTI4MGQtNGIwMi04NTE3LTUzMGM5NDAwNDI1MwAAAAAJAAAAZM1kzSAAAABTWUFORzQtUEMAAAAUAAAAUwBZAEEATgBHADQALQBQAEMAAAAAAAAAAAAAABAAAAB0aXA6Ly9zeWFuZzQtUEMvAAAAAA==</PropagationToken>

</OleTxTransaction>

<a:To s:mustUnderstand="1">net.tcp://localhost:8080/ShoppingCartService</a:To>

</s:Header>

<s:Body>

<AddItemToCart xmlns="http://adventure-works.com/2011/06/08/">

<productNumber>WB-H098</productNumber>

</AddItemToCart>

</s:Body>

</s:Envelope>

</MessageLogTraceRecord>

这是由客户端发送的第一条AddItemToCart消息。该消息值得注意的是<sequence>片段。该片段内的标识符与由服务返回的CreateSequenceResponse消息中的标识符相同。在从客户端发送至服务的所有SOAP消息的header中都需要包含该信息。这些消息还包括一个消息顺序号——在该消息中顺序号为1——该顺序号可以使WCF服务确保消息按照客户端发送的顺序传递至服务实例。你还应该注意<AckRequested>片段。当服务端的WCF运行时接收消息时,它必须向客户端回传一个回执,以使客户端知道其所发送的消息被服务收到。

7.检查第四条消息,它应如下所示

<MessageLogTraceRecord>

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://schemas.xmlsoap.org/ws/2005/02/rm" xmlns:a="http://www.w3.org/2005/08/addressing">

<s:Header>

<r:SequenceAcknowledgement>

<r:Identifier>urn:uuid:60e12f4b-d0ff-4301-a2aa-617a37361808</r:Identifier>

<r:AcknowledgementRange Lower="1" Upper="1"></r:AcknowledgementRange>

<netrm:BufferRemaining xmlns:netrm="http://schemas.microsoft.com/ws/2006/05/rm">8</netrm:BufferRemaining>

</r:SequenceAcknowledgement>

<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/rm/SequenceAcknowledgement</a:Action>

<a:To s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/anonymous</a:To>

</s:Header>

<s:Body></s:Body>

</s:Envelope>

</MessageLogTraceRecord>

这是由WCF服务回传至客户端的回执。<SequenceAcknowledgement>片段内的数据指明的标识符和顺序号表示与AddItemToCart消息中的标识符和顺序号相同。请注意AcknowledgementRange元素表明服务到目前收到的消息的顺序号。一些消息可能已经标明被服务接收到,但是服务仍然保存它们,其目的是作为一个有效保护以防止之前的回执未被客户端接收到。 客户端可以抛弃服务回应的回执。

8.查看第五条消息

<MessageLogTraceRecord>

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://schemas.xmlsoap.org/ws/2005/02/rm" xmlns:a="http://www.w3.org/2005/08/addressing">

<s:Header>

<r:AckRequested>

<r:Identifier>urn:uuid:9736fb3e-e23d-4dd5-8d3b-d2cfab1e50a3</r:Identifier>

</r:AckRequested>

<r:Sequence s:mustUnderstand="1">

<r:Identifier>urn:uuid:9736fb3e-e23d-4dd5-8d3b-d2cfab1e50a3</r:Identifier>

<r:MessageNumber>1</r:MessageNumber>

</r:Sequence>

<a:Action s:mustUnderstand="1">http://adventure-works.com/2011/06/08/ShoppingCartService/AddItemToCartResponse</a:Action>

<a:RelatesTo>urn:uuid:312c55fa-6e04-4e0f-ba1f-241bfe205a2d</a:RelatesTo>

<a:To s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/anonymous</a:To>

</s:Header>

<s:Body>

<AddItemToCartResponse xmlns="http://adventure-works.com/2011/06/08/">

<AddItemToCartResult>true</AddItemToCartResult>

</AddItemToCartResponse>

</s:Body>

</s:Envelope>

</MessageLogTraceRecord>

这是AddItemToCartResponse消息,表明服务把特定的商品成功地添加至购物车。注意该消息要求客户端发送一个回执。<Sequence>片段中使用的标识符与客户端发起会话所使用的标识符相同;并且消息编码为1。如果你检查第六条消息,你将可以看到该消息为客户端发送至服务的回执消息。

9. 检查第七条至第十四条消息。你可以看到所有的事情都相当稳定,并且对话包含客户端发送的请求消息和服务回传的响应消息。这些消息都包含<Sequence>片段,并包含适当的标识符。每个消息都包含一个消息编号,并且这些消息编号随着消息自动增加。

作为一个的优化机制,在初始化请求/响应消息后,收到回执则表明即将进入下一个客户端或服务的请求/响应消息过程——另一句话说即将被发送消息的头部包含已接收到的回执信息。

10. 检查第十五条消息。它应该如下所示:

<MessageLogTraceRecord>

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://schemas.xmlsoap.org/ws/2005/02/rm" xmlns:a="http://www.w3.org/2005/08/addressing">

<s:Header>

<r:SequenceAcknowledgement>

<r:Identifier>urn:uuid:9736fb3e-e23d-4dd5-8d3b-d2cfab1e50a3</r:Identifier>

<r:AcknowledgementRange Lower="1" Upper="5"></r:AcknowledgementRange>

<netrm:BufferRemaining xmlns:netrm="http://schemas.microsoft.com/ws/2006/05/rm">8</netrm:BufferRemaining>

</r:SequenceAcknowledgement>

<r:Sequence s:mustUnderstand="1">

<r:Identifier>urn:uuid:60e12f4b-d0ff-4301-a2aa-617a37361808</r:Identifier>

<r:MessageNumber>6</r:MessageNumber>

<r:LastMessage></r:LastMessage>

</r:Sequence>

<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/rm/LastMessage</a:Action>

<a:To s:mustUnderstand="1">net.tcp://localhost:8080/ShoppingCartService</a:To>

</s:Header>

<s:Body></s:Body>

</s:Envelope>

</MessageLogTraceRecord>

该消息为一条LastMessage消息。它由客户端的WCF运行时发送,并指明该消息为该序列中的最后一条消息;它由客户端程序开始关闭会话时发送。服务端的WCF运行时接收到该消息(第十六条消息),然后发送服务的LastMessage消息(第十七条消息)。然后WCF客户端运行时发送LastMessage的回执消息(第十九条消息)。

11. 检查第18条消息

该消息为TerminateSequencce消息。服务端的WCF运行时发送该消息以指明其将不再使用由该特定标识符制定的序列发送任何消息; WCF客户端的运行时接收到该TerminateSequence消息后,释放该会话使用的资源。请注意服务立即发送该消息而不需要等待来自客户端的LastMessage回执。客户端的WCF运行时同样发送一个TerminateSequence消息至服务(第二十条消息),该消息指明客户端程序向服务发送消息的序列。在该消息交换结束后,会话终止。

11. 关闭服务追踪查看器并删除追踪文件。

通过这些练习,你应该清楚以下两点:

  • 在WCF中非常容易实现可靠的消息传送。你仅仅需要设置绑定的一些属性。你不需要编写任何额外的代码;它对于客户端和服务都是透明的。
  • 可靠的会话将产生额外网络交通量,不仅仅增加了额外的协议消息而且增加了每条消息的大小。在一个会话中,客户端程序发送的消息越多,开销将成比例的减少。

在本章的开始提到了可靠会话独立于可靠的消息传送。但是,反之却不成立,而且你可以使用可靠的消息传送,但不实现可靠的会话。这意味着尽管可靠的消息传送与PerSession实例模式工作良好,它还与PerCall服务实例模式一起发挥作用。在PerCall模式下,虽然WCF运行时为每个连接创建一个新的服务实例,实际上在客户端第一次调用服务时为可靠的消息会话创建序列;当且仅当客户端关闭连接和终止会话时,终止该序列。

你还应该注意并不是所有的绑定配置都支持WS-ReliableMessaging协议。netTcpBinding, wsDualHttpBinding(该绑定始终使用可靠的消息传送,你不能取消它), wsFederationHttpBinding, 和wsHttpBinding。MSMQ绑定系列(msmqIntegrationBinding和netMsmqBinding)使用消息持久化和队列技术而非WS-ReliableMessaging实现可靠的消息传送。常见的绑定比如basicHttpBinding, netNamedPipeBinding和netpeerTcpBinding不支持可靠的消息传送。

识别和处理重放攻击

在第四章"保护企业的WCF服务"中,你已经对重放攻击有一定了解。在重放攻击中,黑客从网络中破译并存贮消息,然后在之后的某一个时间段内发送这些消息。最乐观的情况是该攻击仅仅只是一件让人讨厌的行为。比如,一个黑客破译一个真实的消费者发送至在线的书店的订单后,不停地重放该订单至在线书店的服务;该书店将收到上百个订单,并向真正的消费者发送他并订购的图书。最坏的情况,重放攻击导致大规模的欺骗;一个黑客可能破译真实消费者的银行帐号信息,然后不停地重放消息至该银行的服务器。

使用可靠的会话可以帮助消除简单的重放攻击,因为每个消息必须提供一个合法的序列标识符,并使用唯一的消息编号。当会话完成,序列标识符失效,任何随后的重放消息攻击尝试都会被接收方拒收。然而,考虑下面的设想(hypothetical)场景:如果一个会话是长时间运行的,黑客可能编辑被破译消息内中<Sequence>片段的内容,修改消息编号,设置该编号大于当前的编号,然后当服务会话仍然处于活动状态时发送该消息至服务。当寄宿服务的程序接收到该消息,如果未从真实的客户端收到相同消息编号的消息,宿主程序将缓存该消息,然后当所有的中间消息收到之后将该消息传递至服务实例。而来自客户端的包含相同消息序号的真实消息被服务接收到后,客户端的真实消息将被服务拒绝。那么你该如何处理这种情况?

你可以配置传输安全加密通过消息,然后在网络从一台机器传输该加密后的消息到另外一台机器。此外,传输安全在传输层自动识别重放包。但是记住,传输安全以点到点为基础运行,当服务接收到消息,该服务可以没有任何限制地访问该消息。如果服务被期望继续传送请求消息至运行在其他地方的另外一个服务,那么该服务可以在消息传送之前修改该消息。常见的保护数据的方法是,你不能信任任何中间服务,你需要实现消息安全。但是,消息安全主要关注保护消息的body部分,而非消息的header部分,而消息的序列标识符和消息编号却存贮在header中。

因此,为了避免重放攻击,接收方需要更安全的机制。幸运的是,WCF提供了关于识别重放协议的机制。

在WCF中配置重放识别

当启用重放识别,WCF运行时为每一个消息生成一个任意的,唯一的,签名的,时间戳标识符。这些标识符被视为一次性特性。当接收到消息时,服务使用签名以验证该一次性特性是否被中断,并且提取和检查时间戳以确认该消息为最近发送;然后,服务在缓存中保存该一次性特性。当其他的消息到达时,服务从该消息的header中获取一次性特性。 如果发现该一次性特性与保存在缓存中的一次性特性相匹配,那么说明新收到的消息是之前消息的副本,那么新收到的消息应当被抛弃。如果不相同,那么服务开始处理消息,并添加该消息的一次性特性至缓存。

WCF安全通道默认实现重放识别,尽管使用标准的WCF绑定时,重放识别相关的配置属性不会立即看到。但是,创建自定义的绑定非常容易。你可以通过下面的练习来完成一个自定义的绑定。

为ShoppingCartService服务创建自定义绑定

1. 在visual studio中,使用服务配置工具打开ShoppingCartHost项目的app.config文件。

2. 在配置面板中,点击绑定文件夹。在右边面板中,点击创建绑定。在创建绑定对话框中,选择customBinding,然后点击确认按钮。

3. 在右边面板中,更改该绑定的名字的值为ShoppingCartServiceCustomBindingCfg。

如果你回顾第二章"寄宿WCF服务",WCF运行时创建通道堆栈用以发送和接收消息。入栈消息使用适当的传输协议(比如TCP或HTTP)达到特定的地址(比如一个端口或者一个URL)。当你寄宿一个服务,WCF运行时侦听由客户端指定地址并使用传输协议发送的入栈的请求消息。入栈消息经过传输通道达到转换消息的编码(encoding)通道。 此时,WCF运行时可以使用转换后的数据信息去调用服务中的相关操作。出栈的响应消息将被编码层编码,然后传送至传输通道,最后回传至客户端程序。

一个通道堆栈必须总是至少有两个通道:传输通道和编码通道。当你创建一个自定的绑定,服务配置管理工具将自动为你添加元素:HTTP传输协议和text编码。由于在本章之前的练习使用TCP协议,因此你将更改传输通道。

4. 在右下面板,选择httpTransport栈元素,点击移除,然后添加添加按钮。在添加绑定元素扩展区域对话框中,选择tcpTransport,然后点击添加。

在第二章中强调了一点:通道堆栈中的通道顺序是非常重要的。传输通道必须处于最后一位,并且按照惯例,编码通道位于传输通道之上。确认tcpTranport元素在位置2,并且textMessageEncoding元素在位置1。如果位置编号不是该次序,使用上下键以交换两者的次序。

5. 在右边面板中,再次点击添加按钮。在添加绑定元素扩展区域对话框中,选择security绑定扩展元素,然后点击添加按钮。 使用上下键设置该元素的配置编号。Security元素为1;接着是textMessageEncoding;最后是tcpTransport。

6. 在配置面板中,点击ShoppingCartServiceCustomBindingCfg节点下的安全节点。在右边面板中,设置AuthenticationMode属性为SecureConversation。该模式实现WS-SecureConversation规范,在服务和客户端程序之间建立安全的会话。

7. 在右边面板中,点击服务标签。确认DetectReplay属性设置为True。

8. 检查ReplayCacheSize属性

启动重放识别后,服务端的WCF运行时将内存中缓存一次性特性。缓存的一次性特性的数量确定了将占用的最大内存。如果达到限制,最老的一次性特性将从缓存中移除;然后最新的一次性特性添加到缓存中。ReplayCacheSize属性的默认值为(900000)满足大多数情况,但是你可能期望减少内存的使用量特别是当内存容量不是特别大时。然后,你不能将该值设置的太小以至于一次性特性很快被丢弃;这样会导致服务再次遭到重放攻击。

9. 检查ReplayWindow和MaxClockSkew属性

ReplayWindow属性用以指定持续一次性特性被视为有效的周期(默认值为5分钟)。如果接收到的一次性特性的时间戳如果超出该属性指定的范围,该一次性特性将将因为过期而被丢弃。WCF可以识别两台计算机上的系统时钟可能不完全同步。 为了补偿,你可以使用MaxClockSkew属性以指定最大时钟差异的许可范围。如果服务器的时钟比较慢,一次性特性的时间戳可能在将来的一段时间之后才达到,因此MaxClockSKew属性允许服务接受包含将来时间的一次性特性——只要该时间在指定的范围之内。

10. 在配置面板中,点击ShoppingCartServiceCustomBindingCfg节点。ShoppingCartService服务使用事务和可靠的会话,因为你必须添加实现这些特性的通道。

在右下面板,点击添加。在添加绑定元素扩展区域对话框,选择reliableSession绑定扩展元素,然后点击添加

重复上诉过程,添加transactionFlow绑定扩展元素

重新排列通道堆栈:transactionFlow元素排第一位,reliableSession排第二位,security元素排第三位,textMessageEncoding排第四位,tcpTransport排第五

11. 在配置面板,展开服务文件夹下的ShoppingCartService.ShoppingCarService服务,然后在服务端点上点击右键,然后添加新的服务端点。在右边面板中,使用下表的内容设置该端点的属性

属性

Name

ShoppingCartServiceCustomEndpoint

Address

Net.tcp://localhost:8090/ShoppingCartService

Binding

customBinding

BindConfiguration

ShoppingCartServiceCustomBindingCfg

Contract

ShoppingCartService.IShoppingCartService


12. 保存配置文件,并退出WCF服务配置工具

现在你可以添加一个相应的客户端绑定,然后配置客户端使用该绑定

为ShoppingCartService客户端创建自定义绑定

1. 在Visual Studio中,使用WCF服务配置工具编辑ShoppingCartClient项目的App.config文件。

2. 在配置面板中,展开绑定节点,添加一个新的customBinding绑定配置,并设置该绑定配置的名字为ShoppingCartClientCustomBindingCfg

3. 移除httpTransport元素,并添加netTranspaort元素

4. 添加安全元素,并设置AuthenticationMode属性为SecureConversation

5. 添加reliableSession元素和transactionFlow元素至自定义绑定

6. 重新排列通道堆栈:transactionFlow元素排第一位,reliableSession排第二位,security元素排第三位,textMessageEncoding排第四位,tcpTransport排第五

7. 在配置面板,添加新的客户都端点。在右边面板中,使用下表的内容设置该端点的属性

属性

Name

ShoppingCartServiceCustomEndpoint

Address

Net.tcp://localhost:8090/ShoppingCartService

Binding

customBinding

BindConfiguration

ShoppingCartClientCustomBindingCfg

Contract

ShoppingCartClient.ShoppingCartService.IShoppingCartService


8. 保存配置文件,并退出WCF服务配置工具

9. 在Visual Studio中,打开ShoppingCartClient项目下的Programm.cs文件,修改proxy的声明为

ShoppingCartServiceClient proxy = new ShoppingCartServiceClient("CustomBinding_IShoppingCartService");


10. 在非调试模式下运行解决方案。在ShoppingCartClient控制台窗口中将显示"Press ENTER when the service has started",然后按下ENTER键

客户端程序与之前章节的运行结果一样,但是现在它使用启用了重放识别的自定义绑定与ShoppingCartService服务通信。

11. 按ENTER键关闭客户端程序窗口。在宿主程序控制台窗口中,按ENTER键停止并关闭服务。

当你运行客户端程序,经过很小但能注意到的延迟后,客户端程序才会显示从服务接收到的第一条响应消息。如果你使用服务追踪查看器检查追踪文件,你将发现为什么会这样。安全对话协议生成了相当数量的用于建立安全环境的消息。此外,每条由客户端程序发送的消息、以及服务回传的响应消息的body部分,都已经签名、加密,并且包含了一次性特性信息。因为,尽管安全会话协议提供非常好的保护,但也为之付出一定代价。如果你构建客户端程序和服务是直接通信的(点对点),那么你会发现采用传输安全更有效。

【总结】

在本章,你配置WCF服务和客户端程序通过可靠地会话进行通信;你还看到WCF如何实现WS-ReliableMessaging规范规定,以及WCF如何使用序列、消息编号、和回执消息验证消息已经被收到并且按照正确的顺序存放在缓存中;最后,你还看到如何创建可识别重放的自定义绑定。

【参考】

[1]. WS-ReliableMessaging规范http://msdn.microsoft.com/en-us/library/ms951271.aspx

[2] How to: Enable Message Replay Detection http://msdn.microsoft.com/en-us/library/ms733063.aspx

posted @ 2011-07-09 12:27  On the road....  阅读(2264)  评论(0编辑  收藏  举报