问题
I'm using ASP.Net Web API 2 / .Net 4.5.2.
I'm trying to retain the calling principal when queueing a background work item. To that end, I'm trying to:
Thread.CurrentPrincipal = callingPrincipal;
But when I do so, I get an ObjectDisposedException:
System.ObjectDisposedException: Safe handle has been closed
How do I keep the current principal inside the background work item?
Can I make a copy of the principal somehow?
public void Run<T>(Action<T> action)
{
_logger.Debug("Queueing background work item");
var callingPrincipal = Thread.CurrentPrincipal;
HostingEnvironment.QueueBackgroundWorkItem(token =>
{
try
{
// UNCOMMENT - THROWS EXCEPTION
// Thread.CurrentPrincipal = callingPrincipal;
_logger.Debug("Executing queued background work item");
using (var scope = DependencyResolver.BeginLifetimeScope())
{
var service = scope.Resolve<T>();
action(service);
}
}
catch (Exception ex)
{
_logger.Fatal(ex);
}
finally
{
_logger.Debug("Completed queued background work item");
}
});
}
回答1:
Turns out ClaimsPrincipal
now has a copy constructor.
var principal = new ClaimsPrincipal(Thread.CurrentPrincipal);
This appears to resolve the issue while retaining all of the identity and claims information. The complete function follows:
public void Run<T>(Action<T> action)
{
_logger.Debug("Queueing background work item");
var principal = new ClaimsPrincipal(Thread.CurrentPrincipal);
HostingEnvironment.QueueBackgroundWorkItem(token =>
{
try
{
Thread.CurrentPrincipal = principal;
_logger.Debug("Executing queued background work item");
using (var scope = DependencyResolver.BeginLifetimeScope())
{
var service = scope.Resolve<T>();
action(service);
}
}
catch (Exception ex)
{
_logger.Fatal(ex);
}
finally
{
_logger.Debug("Completed queued background work item");
}
});
}
回答2:
The problem of your situation is that the background task being executed after the Thread.CurrentPrincipal
being disposed. This happens because of ASP.NET model - the request is being handled in user context and after that all the values corresponding to the user are freed. So this is exactly happens to your identity. Try to save the information about the user and his identity to impersonate it later.
You can review a support article from Microsoft for impersonating the operations in ASP.NET sites, but I don't think that this will be helpful for you:
System.Security.Principal.WindowsImpersonationContext impersonationContext;
impersonationContext =
((System.Security.Principal.WindowsIdentity)callingPrincipal.Identity).Impersonate();
//Insert your code that runs under the security context of the authenticating user here.
impersonationContext.Undo();
or, may be, you can use the User.Token, something like this:
HostingEnvironment.QueueBackgroundWorkItem(token =>
{
try
{
_logger.Debug("Executing queued background work item");
using (HostingEnvironment.Impersonate(callingPrincipal.Identity))
{
using (var scope = DependencyResolver.BeginLifetimeScope())
{
var service = scope.Resolve<T>();
action(service);
}
}
// UNCOMMENT - THROWS EXCEPTION
// Thread.CurrentPrincipal = callingPrincipal;
}
catch (Exception ex)
{
_logger.Fatal(ex);
}
finally
{
_logger.Debug("Completed queued background work item");
}
});
I suggest you to review your architecture design so you can find a way to move out the background operation to other context, in which the user identity would stay longer. Other way, for example, is to use the passing the current OperationContext
to the Task
:
// store local operation context
var operationContext = OperationContext.Current;
TaskFactory.StartNew(() =>
{
// initialize the current operation context
OperationContext.Current = operationContext;
action();
})
来源:https://stackoverflow.com/questions/35460292/retaining-principal-inside-queued-background-work-item