提高Windows Communication Foundation (WCF) 应用程序负载能力的方法之一就是通过把它们部署到负载均衡的服务器场中. 其中可以使用标准的负载均衡技术, Windows 网络负载均衡(NLB)的软件(例如 Application Request Routing), 或者硬件(F5)实现NLB的功能. 随着这些NLB场景变得越来越复杂, 对WCF的架构带来了越来越多的挑战. 本文仅对wsHttpBinding+Message Security+Windows Authentication在NLB的环境下最常见的文件进行讨论..
%26nbsp;
介绍 :
%26nbsp;
通过阅读本文, 您可以了解到:
- NLB下wsHttpBinding+Message Security+Windows Authentication常见的异常.`
- WCF Message Security的Windows Authentication是如何进行的.
- 提供几种典型的WCF Security的配置方式.
%26nbsp;
预备知识 :
%26nbsp;
WCF wsHttpBinding默认采用Message Security模式. 相比Transport Security模式,它有很多优点。例如:提供了End-to-End security, 允许选择性的加密部分消息,与传输协议无关可以用于任何传输协议,提供更多的安全选择。更多内容可以参考 :
http://msdn.microsoft.com/en-us/library/ff648863.aspx
http://msdn.microsoft.com/en-us/library/ms733137(v=vs.110).aspx
%26nbsp;
Message Security 下有多种客户端验证方式。 其中Windows authentication是比较常用的Credential Type,并且是默认值。更多内容可以参考:
http://msdn.microsoft.com/en-us/library/ms731346(v=vs.110).aspx
%26nbsp;
%26nbsp;
常见问题 :
%26nbsp;
在Standalone的服务器上WCF Security能够很好的工作. 但是在NLB的环境上, 由于错误的配置方式, 可能会遇到下面的常见错误 :
%26nbsp;
%26nbsp;
%26nbsp;
WsHttpBinding+Message Security+Windows Authentication是如何工作的 :
%26nbsp;
要了解这两个错误是如何发生的, 首先需要了解正常情况下wsHttpBinding Message Security是如何进行Windows 验证的.
%26nbsp;
一般情况下, wsHttpBinding按照SPNEGO来引导一个SecurityContextToken(SCT)的生成, 并且将SCT缓存到服务器端. STCs 是基于对称密钥生成的,能够非常有效的签注/加密(signing/encrypting)消息。 当确定客户端会向服务器端发送多次请求的时候,这是一种非常好的提供效率的方式。通过观察WCF TRACE,一般来说会看到这样的过程。首先客户端和服务端生成一个SCT,然后进行一个SPNEGO的过程(有可能是多次请求才能完成),最后客户端和服务端交换SCT并且使用SCT加密WCF的调用。
下面比较详细的列举出整个步骤
- 客户端首先进行SP-Nego 握手, 这个过程完成后会生成一个SCT。
1)%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp; 客户端先发送一个Request Security Token(RST)。RST的目的是生成SP-Nego TokenType,它包含了一个SP-Nego块.
2)%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp; 服务端返回一个Request Security Token Response (RSTR).
3)%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp; 如果需要的话,客户端会将RSTR再次发送给服务端.
4)%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp; 服务端将一个对称密钥包裹在STC里, 又将STC包裹在Request Security Token Response Collection (RSTRC). 然后把RSTRC发送给客户端.
一般来说3)和4)可能会发生多次, 一般情况下可能会有4次, 2次客户端2次服务端.
%26nbsp;
- 客户端向服务端请求另外一个SCT用于消息层面的security。这个SCT是一个长生命是周期,并且真正代表了客户端和服务端之间的session key.
1)%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp; 客户端用SCT加注并且加密RST, 并把RST发送到服务端.
2)%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp; 服务器端用SCT加注并加密一个RSTR并发送会客户端. 这个RSTR中包含了一个SCT.
这里仅发生一次RST/RSTR.
%26nbsp;
- 客户端在得到 2 所产生的SCT之后,开始执行WCF调用。
1)%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp; 客户端使用上一步所得到的SCT来加密消息. 进行WCF调用.
2)%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp; 服务端执行完成之后, 使用同一的SCT来加密消息并返回给客户的. 在这里addressing headers会被签注, 消息体会被签注并且加密.
%26nbsp;
问题分析 :
%26nbsp;
问题一 :
%26nbsp;
The SecurityContextSecurityToken with context-id=urn:uuid:a02a1879-3297-4dee-8035-68eb30ed4195 (key generation-id=) is not registered.
at System.ServiceModel.Security.WSSecureConversation.SecurityContextTokenEntry.ReadTokenCore(XmlDictionaryReader reader, SecurityTokenResolver tokenResolver)
at System.ServiceModel.Security.WSSecurityTokenSerializer.ReadTokenCore(XmlReader reader, SecurityTokenResolver tokenResolver)
at System.IdentityModel.Selectors.SecurityTokenSerializer.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver)
at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver, IList1 allowedTokenAuthenticators, SecurityTokenAuthenticator%26amp;amp;amp; usedTokenAuthenticator)
at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlDictionaryReader reader, Int32 position, Byte[] decryptedBuffer, SecurityToken encryptionToken, String idInEncryptedForm, TimeSpan timeout)
at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader)
at System.ServiceModel.Security.StrictModeSecurityHeaderElementInferenceEngine.ExecuteProcessingPasses(ReceiveSecurityHeader securityHeader, XmlDictionaryReader reader)
at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout)
at System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(ReceiveSecurityHeader securityHeader, Message%26amp;amp;amp; message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
at System.ServiceModel.Security.SymmetricSecurityProtocol.VerifyIncomingMessageCore(Message%26amp;amp;amp; message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message%26amp;amp;amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
at System.ServiceModel.Channels.SecurityChannelListener1.ServerSecurityChannel1.VerifyIncomingMessage(Message%26amp;amp;amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationState)
at System.ServiceModel.Channels.SecurityChannelListener1.SecurityReplyChannel.ProcessReceivedRequest(RequestContext requestContext, TimeSpan timeout)
at System.ServiceModel.Channels.SecurityChannelListener1.ReceiveRequestAndVerifySecurityAsyncResult.ProcessInnerItem(RequestContext innerItem, TimeSpan timeout)
at System.ServiceModel.Channels.SecurityChannelListener1.ReceiveItemAndVerifySecurityAsyncResult2.OnInnerReceiveDone()
at System.ServiceModel.Channels.SecurityChannelListener1.ReceiveItemAndVerifySecurityAsyncResult2.InnerTryReceiveCompletedCallback(IAsyncResult result)
at System.ServiceModel.Diagnostics.Utility.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.Channels.InputQueue1.AsyncQueueReader.Set(Item item)
at System.ServiceModel.Channels.InputQueue1.Dispatch()
at System.ServiceModel.Channels.InputQueue1.OnDispatchCallback(Object state)
at System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke2()
at System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.OnSecurityContextCallback(Object o)
at System.Security.SecurityContext.Run(SecurityContext securityContext, ContextCallback callback, Object state)
at System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke()
at System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ProcessCallbacks()
at System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.CompletionCallback(Object state)
at System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ScheduledOverlapped.IOCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
at System.ServiceModel.Diagnostics.Utility.IOCompletionThunk.UnhandledExceptionFrame(UInt32 error, UInt32 bytesRead, NativeOverlapped* nativeOverlapped)
at System.Threading.IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped pOVERLAP)
%26nbsp;
这个错误在服务端和客户端均能看到, 但大多数情况下是在服务端发生, 并且将这一的错误饭或到了客户端. 这个错误的字面意思就是找不到SCT. 但是既然能够肯到STC的UUID, 那么说明这个SCT确实存在.
参照上面STC生成的过程可以看到, STC是服务端和客户端进过多次的交涉最终才能形成. 这个STC只能在这个服务端和客户端之间传递, 并不能分享给第三方. 这是出于安全的考虑.
这个问题最常见于NLB的环境中.如果NLB设备或者软件没有启用Sticky Session. 在有多台WCF服务器的情况下, 客户端发起的请求很有可能被转移到另外一台服务器上. 由于STC并不会被第三方获取, 那么另外一个服务器上不会有相同的SCT. 因此服务端会认为这个STC并没有被注册.
%26nbsp;
%26nbsp;
问题二:
%26nbsp;
%26lt;ExceptionType%26gt;System.ServiceModel.Security.SecurityNegotiationException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089%26lt;/ExceptionType%26gt;
%26lt;Message%26gt;Cannot find the negotiation state for the context 'uuid-954e94c0-6e42-4816-b53f-bc75e18a9534-2'.%26lt;/Message%26gt;
%26lt;StackTrace%26gt;
at System.ServiceModel.Security.NegotiationTokenAuthenticator`1.ProcessRequestCore(Message request)
at System.ServiceModel.Security.NegotiationTokenAuthenticator`1.NegotiationHost.NegotiationSyncInvoker.Invoke(Object instance, Object[] inputs, Object[]%26amp;amp; outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc%26amp;amp; rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc%26amp;amp; rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc%26amp;amp; rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc%26amp;amp; rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc%26amp;amp; rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc%26amp;amp; rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
at System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(IAsyncResult result)
at System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(IAsyncResult result)
at System.ServiceModel.Diagnostics.Utility.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.Channels.InputQueu