Wif 保护 WCF 服务,缓存令牌 - 异步 wcf 方法丢失身份
我有一个使用 WIF 保护的 wcf 服务。我正在客户端(网站)上实现令牌缓存,如 Travis Spencer 的博客中所述:
http://travisspencer.com/blog/2009/03/caching-tokens-to-avoid-calls.html
该网站正在使用模拟(冒充我),并且STS wcf 端点配置为使用 Windows 身份验证。
当使用直接调用(即非异步调用)调用 WCF 服务时,令牌缓存工作正常 - 删除 ClientCredentials 行为并添加我的自定义 CacheClientCredentials 行为,并且令牌在第一次调用时缓存并在后续调用中重用。
但是,我有一个场景,其中在 wcf 服务上调用异步方法,并提供回调。在正常情况下(非异步),CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider 方法会被多次调用,并且每次调用时线程运行的身份都是正确的。对 STS 的后续调用使用正确的凭据,对用户进行身份验证并返回令牌。调用异步方法时,会发生对 CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider 的多次调用,但只有第一次调用具有正确的标识。后续调用以“NT AUTHORITY\NETWORK SERVICE”作为标识。结果,对STS的调用凭证错误,认证失败。 (STS 日志显示一条消息,指示“无法进行身份验证”。
我一直在尝试在 Begin/End 异步方法周围添加显式模拟,但这并不总是有效。然后,我将以下值添加到网络中。配置:
<legacyImpersonationPolicy enabled="false" />
<alwaysFlowImpersonationPolicy enabled="true" />
这也并不总是有效(尽管有时有效),这里的不寻常之处是业务逻辑涉及 3 次调用异步方法(如果调用失败),我发现通常前 2 次失败,而后 2 次失败。第三次成功 - 即 CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider 通常前两次以错误的身份运行,第三次以正确的身份运行 - 但这似乎也有点随机。当 CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider 以正确的身份成功时,WCF 调用也会成功。但是,令牌不会添加到缓存中(不会执行令牌缓存代码),随后调用非异步方法,然后构造一个新的缓存,然后获取令牌,将其添加到缓存中。
调用使用 WIF 保护的异步 WCF 方法以确保跨调用缓存 WIF 令牌的正确方法是什么?
是否需要特殊的身份配置来确保在所有这些进程中使用相同的身份? (所有区域都在使用模拟)
更新:
我不确定它是否增加了很多,但我发现当代码不起作用时,以下是 CacheClientCredentialsSecurityTokenManager 中的堆栈跟踪:
Unflagged > 5732 18 Worker Thread <No Name> CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider Normal
MySecurity.dll!CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider(System.IdentityModel.Selectors.SecurityTokenRequirement tokenRequirement) Line 18
System.ServiceModel.dll!System.ServiceModel.Security.SecurityProtocol.AddSupportingTokenProviders(System.ServiceModel.Security.Tokens.SupportingTokenParameters supportingTokenParameters, bool isOptional, System.Collections.Generic.IList<System.ServiceModel.Security.SupportingTokenProviderSpecification> providerSpecList) + 0xca bytes
System.ServiceModel.dll!System.ServiceModel.Security.SecurityProtocol.OnOpen(System.TimeSpan timeout) + 0xa7 bytes
System.ServiceModel.dll!System.ServiceModel.Security.SymmetricSecurityProtocol.OnOpen(System.TimeSpan timeout) + 0x45 bytes
System.ServiceModel.dll!System.ServiceModel.Security.OperationWithTimeoutAsyncResult.OnScheduled(object state) + 0x82 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke2() + 0x46 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.OnSecurityContextCallback(object o) + 0x28 bytes
mscorlib.dll!System.Security.SecurityContext.Run(System.Security.SecurityContext securityContext, System.Threading.ContextCallback callback, object state) + 0x55 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke() + 0x4d bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ProcessCallbacks() + 0x180 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.CompletionCallback(object state) + 0x7a bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ScheduledOverlapped.IOCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped) + 0xf bytes
SMDiagnostics.dll!System.ServiceModel.Diagnostics.Utility.IOCompletionThunk.UnhandledExceptionFrame(uint error, uint bytesRead, System.Threading.NativeOverlapped* nativeOverlapped) + 0x3d bytes
mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) + 0x54 bytes
[Appdomain Transition]
当它起作用时,它是有点不同(注意额外的 mscorlib 条目和中间的转换):
Unflagged > 5408 12 Worker Thread <No Name> CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider Normal
MySecurity.dll!CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider(System.IdentityModel.Selectors.SecurityTokenRequirement tokenRequirement) Line 18
System.ServiceModel.dll!System.ServiceModel.Security.SecurityProtocol.AddSupportingTokenProviders(System.ServiceModel.Security.Tokens.SupportingTokenParameters supportingTokenParameters, bool isOptional, System.Collections.Generic.IList<System.ServiceModel.Security.SupportingTokenProviderSpecification> providerSpecList) + 0xca bytes
System.ServiceModel.dll!System.ServiceModel.Security.SecurityProtocol.OnOpen(System.TimeSpan timeout) + 0xa7 bytes
System.ServiceModel.dll!System.ServiceModel.Security.SymmetricSecurityProtocol.OnOpen(System.TimeSpan timeout) + 0x45 bytes
System.ServiceModel.dll!System.ServiceModel.Security.OperationWithTimeoutAsyncResult.OnScheduled(object state) + 0x82 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke2() + 0x46 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.OnSecurityContextCallback(object o) + 0x28 bytes
***mscorlib.dll!System.Security.SecurityContext.runTryCode(object userData) + 0x6e bytes
[Native to Managed Transition]
[Managed to Native Transition]
***mscorlib.dll!System.Security.SecurityContext.RunInternal(System.Security.SecurityContext securityContext, System.Threading.ContextCallback callBack, object state) + 0xc2 bytes
***mscorlib.dll!System.Security.SecurityContext.Run(System.Security.SecurityContext securityContext, System.Threading.ContextCallback callback, object state) + 0xca bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke() + 0x4d bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ProcessCallbacks() + 0x180 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.CompletionCallback(object state) + 0x7a bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ScheduledOverlapped.IOCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped) + 0xf bytes
SMDiagnostics.dll!System.ServiceModel.Diagnostics.Utility.IOCompletionThunk.UnhandledExceptionFrame(uint error, uint bytesRead, System.Threading.NativeOverlapped* nativeOverlapped) + 0x3d bytes
mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) + 0x54 bytes
[Appdomain Transition]
I have a wcf service that is secured using WIF. I'm implementing token caching on the client (a website), as described in Travis Spencer's blog here:
http://travisspencer.com/blog/2009/03/caching-tokens-to-avoid-calls.html
The website is using impersonation (impersonating me), and the STS wcf endpoint is configured to use Windows authentication.
When calling the WCF service using direct calls (i.e. non async calls), token caching works fine - the ClientCredentials behavior is removed and my custom CacheClientCredentials behavior is added, and the token is cached on the first call and reused on the subsequent call.
However I have a scenario in which an async method is called on the wcf service, with a callback being provided. Under normal circumstances (non async) the CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider method is called multiple times, and on each call the identity the thread is running as is correct. The subsequent call to the STS is using the correct credentials, the user is authenticated and the token returned. When the async method is called, the multiple calls to CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider occur, but only the first call has the correct identity. The subsequent calls have "NT AUTHORITY\NETWORK SERVICE" as the identity. As a result, the call to the STS has the wrong credentials, and the authentication fails. (The STS log shows a message indicating " could not be authenticated".
I have been trying adding explicit impersonation around the Begin/End async methods, but this didn't work all the time. I then added the following values to the web.config:
<legacyImpersonationPolicy enabled="false" />
<alwaysFlowImpersonationPolicy enabled="true" />
This also doesn't always work (although sometimes does). The unusual thing here is that the business logic involves 3 attempts to call the async method (if calls fail). I am finding that generally the first 2 fail, and the third succeeds - i.e. CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider normally runs as the wrong identity the first two times, and the correct identity the third time - but this appears to be a bit random too. When CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider succeeds with the right identity, the WCF call succeeds. However the token is not added to the cache (no token cache code is executed) Subsequent calls to a non async method then construct a new cache and then get the token, adding it to the cache.
What is the correct method to call an asynchronous WCF method that is secured using WIF, to ensure that the WIF token is cached across calls?
Is there special identity configuration required to ensure the same identity is used across all of these processes? (all areas are using impersonation)
Update:
I'm not sure if it adds much, but I've found that when the code doesn't work, the following is the stacktrace while in the CacheClientCredentialsSecurityTokenManager:
Unflagged > 5732 18 Worker Thread <No Name> CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider Normal
MySecurity.dll!CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider(System.IdentityModel.Selectors.SecurityTokenRequirement tokenRequirement) Line 18
System.ServiceModel.dll!System.ServiceModel.Security.SecurityProtocol.AddSupportingTokenProviders(System.ServiceModel.Security.Tokens.SupportingTokenParameters supportingTokenParameters, bool isOptional, System.Collections.Generic.IList<System.ServiceModel.Security.SupportingTokenProviderSpecification> providerSpecList) + 0xca bytes
System.ServiceModel.dll!System.ServiceModel.Security.SecurityProtocol.OnOpen(System.TimeSpan timeout) + 0xa7 bytes
System.ServiceModel.dll!System.ServiceModel.Security.SymmetricSecurityProtocol.OnOpen(System.TimeSpan timeout) + 0x45 bytes
System.ServiceModel.dll!System.ServiceModel.Security.OperationWithTimeoutAsyncResult.OnScheduled(object state) + 0x82 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke2() + 0x46 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.OnSecurityContextCallback(object o) + 0x28 bytes
mscorlib.dll!System.Security.SecurityContext.Run(System.Security.SecurityContext securityContext, System.Threading.ContextCallback callback, object state) + 0x55 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke() + 0x4d bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ProcessCallbacks() + 0x180 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.CompletionCallback(object state) + 0x7a bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ScheduledOverlapped.IOCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped) + 0xf bytes
SMDiagnostics.dll!System.ServiceModel.Diagnostics.Utility.IOCompletionThunk.UnhandledExceptionFrame(uint error, uint bytesRead, System.Threading.NativeOverlapped* nativeOverlapped) + 0x3d bytes
mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) + 0x54 bytes
[Appdomain Transition]
When it does work, it is a bit different (note the additional mscorlib entries, and transitions in the middle):
Unflagged > 5408 12 Worker Thread <No Name> CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider Normal
MySecurity.dll!CacheClientCredentialsSecurityTokenManager.CreateSecurityTokenProvider(System.IdentityModel.Selectors.SecurityTokenRequirement tokenRequirement) Line 18
System.ServiceModel.dll!System.ServiceModel.Security.SecurityProtocol.AddSupportingTokenProviders(System.ServiceModel.Security.Tokens.SupportingTokenParameters supportingTokenParameters, bool isOptional, System.Collections.Generic.IList<System.ServiceModel.Security.SupportingTokenProviderSpecification> providerSpecList) + 0xca bytes
System.ServiceModel.dll!System.ServiceModel.Security.SecurityProtocol.OnOpen(System.TimeSpan timeout) + 0xa7 bytes
System.ServiceModel.dll!System.ServiceModel.Security.SymmetricSecurityProtocol.OnOpen(System.TimeSpan timeout) + 0x45 bytes
System.ServiceModel.dll!System.ServiceModel.Security.OperationWithTimeoutAsyncResult.OnScheduled(object state) + 0x82 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke2() + 0x46 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.OnSecurityContextCallback(object o) + 0x28 bytes
***mscorlib.dll!System.Security.SecurityContext.runTryCode(object userData) + 0x6e bytes
[Native to Managed Transition]
[Managed to Native Transition]
***mscorlib.dll!System.Security.SecurityContext.RunInternal(System.Security.SecurityContext securityContext, System.Threading.ContextCallback callBack, object state) + 0xc2 bytes
***mscorlib.dll!System.Security.SecurityContext.Run(System.Security.SecurityContext securityContext, System.Threading.ContextCallback callback, object state) + 0xca bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.WorkItem.Invoke() + 0x4d bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ProcessCallbacks() + 0x180 bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.CompletionCallback(object state) + 0x7a bytes
System.ServiceModel.dll!System.ServiceModel.Channels.IOThreadScheduler.CriticalHelper.ScheduledOverlapped.IOCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped) + 0xf bytes
SMDiagnostics.dll!System.ServiceModel.Diagnostics.Utility.IOCompletionThunk.UnhandledExceptionFrame(uint error, uint bytesRead, System.Threading.NativeOverlapped* nativeOverlapped) + 0x3d bytes
mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) + 0x54 bytes
[Appdomain Transition]
在尝试调用异步方法之前,我没有在代理上显式调用
.Open()
,因此.Open()
似乎在内部发生客户端代理,在另一个线程上 - 因此存在身份问题。我发现,如果我调用:缓存的凭据设置和令牌缓存检查会同步发生,因此使用正确的身份,并按预期运行。
I have not been explicitly calling
.Open()
on the proxy before attempting to call the async method, and as a result the.Open()
appears to happen internally within the client proxy, on another thread - hence the identity issues. I found that if I called:the cached credentials setup and token cache checking happens synchronously, and therefore using the correct identity, and functions as expected.