我在服务器上运行WCF服务,该服务器配置为接受Kerberos身份验证。
Kerberos工作正常,因此WCF服务知道哪个用户正在连接到他。 该服务提供一切作为异步方法。就像这里一样(只是一个清晰的例子)。
public ExampleService : IExampleService {
public Task<string> GetUsernameAsync() {
return await Task.Run(() => System.Threading.Thread.CurrentPrincipal.Name);
}
}
在客户端,我有一个Controller(它是一个MVC页面,但这没关系),它会异步调用这些方法。
public ExampleController {
public async Task<ActionResult> Index() {
using(var serviceClient = ServiceFactory.GetServiceClient())
using(Security.Impersonation.Impersonate())
{
var data = await serviceClient.GetUsernameAsync();
return View(data);
}
}
}
只要我不使用等待,模仿就可以正常工作。
由于Task<>
没有流出模拟身份,我想知道是否有可能更改Task
的执行用户或做其他事情以使模拟工作在这个用例中。
我尝试了一个自定义awaiter(因为它可以在那个案例中使用Culture),但这根本不起作用(好吧它也没有冒充)。
答案 0 :(得分:5)
好的 - 经过一些更深入的研究后,我终于找到了解决方案如何在异步任务中传播模拟的Windows身份。
该解决方案是机器范围的,将为所有(在本例中)64位ASP.NET 4.5应用程序设置。
在aspnet.config
中找到C:\Windows\Microsoft.Net\Framework64\v4.0.30319
文件(可能这也适用于更高版本)并将legacyImpersonationPolicy
的值更改为false
<legacyImpersonationPolicy enabled="false"/>
确保重新启动IIS(或重启机器) 只要您使用托管的方法进行模拟,这将使模拟流动。在我的情况下,我冒充类似于此,这工作正常:
class Impersonation : IDisposable
{
public static Impersonation Impersonate()
{
return new Impersonation();
}
private WindowsImpersonationContext ImpersonationContext { get; set; }
private Impersonation()
{
var currentIdentity = System.Threading.Thread.CurrentPrincipal.Identity as WindowsIdentity;
if (currentIdentity != null && currentIdentity.IsAuthenticated)
{
ImpersonationContext = currentIdentity.Impersonate();
return;
}
throw new SecurityException("Could not impersonate user identity");
}
public void Dispose()
{
if(ImpersonationContext != null)
ImpersonationContext.Dispose();
}
}
}
这里解释了aspnet.config设置(顺便说一下。它在web.config文件中没有设置它):http://msdn.microsoft.com/en-us/library/ms229296(v=vs.110).aspx(基本上说,如果这是真的,我们这样做.NET 1.1方式)
您可以使用此方法检查窗口标识是否流动:
System.Security.SecurityContext.IsWindowsIdentityFlowSuppressed()
答案 1 :(得分:3)
我不同意你的问题。
问题不在于您await
。但是你的Task.Run
。 ASP.Net代码上应该没有await Task.Run
。它的效果是一个不必要的线程切换。由于ASP.Net上没有STA线程,因此不需要这样做,只会减慢代码速度。
如果您坚持使用真正的无线Task
,那么您应该没有任何问题,因为您将保留在一个线程中。除非您的应用程序服务器具有非常有限的客户端数量和大量的CPU绑定操作,否则多线程不适合扩展,因为单个用户可以快速填写服务器的计划。
您应该使用Task.FromResult
或TaskCompletionSource.Task
来确保您保持单线程。通过[ThreadLocal]
属性可以解决您的问题。
TL:DR
请勿在服务器端使用Task.Run
。使用Task.FromResult
,因此您只有一个帖子。
编辑:响应
哪个帖子?在客户端,您仍将使用await
。我从未说过不要使用await
。我说过不要直接使用await
Task.Run
(除了UI线程)。我没有说你应该BLOCK一个线程。因为你的线程应该做WORK来产生你传递给Task.FromResult
的结果。 BLOCKING意味着你的线程什么都不做,同时消耗资源(即内存)。哎呀,甚至没有必要
服务器端应使用此模式:
public ExampleService : IExampleService
{
public Task<string> GetUsernameAsync()
{
var name = System.Threading.Thread.CurrentPrincipal.Name;
return Task.FromResult(name);
}
}
客户端应该保持
public ExampleController
{
public async Task<ActionResult> Index()
{
using(var serviceClient = ServiceFactory.GetServiceClient())
using(Security.Impersonation.Impersonate())
{
var data = await serviceClient.GetUsernameAsync();
return View(data);
}
}
}
并且在ServiceClient在本地解析的情况下,所有内容都会同步运行(速度更快,资源更少)。这里的要点是,您只对There is no thread样式的异步应用Task
async
模式。 Task.Run
是async的并发风格,只应在需要使用另一个线程时使用(因为你是CPU绑定的,或者这个线程需要用于其他东西)。
答案 2 :(得分:0)
由于我负责WCF接口,因此这是一个有效的解决方案(但我不喜欢,因为它或多或少是代码重复):
[ServiceContract]
interface IExampleService {
[OperationContract]
string GetUsername();
}
interface IExampleServiceAsync {
Task<string> GetUserNameAsync();
}
class ExampleService : IExampleService {
public string GetUsername() {
return System.Threading.Thread.CurrentPrincipal.Name;
}
}
class ExpampleServiceClient : ServiceClient<IExampleService>, IExampleServiceAsync {
public Task<string> GetUsernameAsync() {
return Task.Run(() => GetUsername());
}
private string GetUsername() {
using(Security.Impersonation.Impersonate())
{
return base.Proxy.GetUsername();
}
}
}
我不得不说这是一种解决方法 - 而不是解决方案 - 它改变了服务器端的接口(仅限非Async接口),但至少它正在运行。
此解决方案的一个优点 - 您可以将模拟实现为ExampleServiceClient之上的行为模式。