WindowsIdentity.Impersonate() confusion

2019-07-15 10:58发布

问题:

I have a web application hosted by IIS. it is configured with Form Authentication and Anonymous authentication, and Impersonation is enabled. App Pool account is Network Service. Anonymous account is Costa. Costa is having access to database. NetworkService cannot access database.

The problem is that Request thread (parent thread) can access the database but sub thread cannot.

To fix this. I send windows identity object of main thread to sub thread, then I call Impersonate(). Impersonate means "assign current thread Windows Identity with impersonated account. My question: Is that a good practice? Is there a risk?

\\Request thread code (Parent thread)

\\WindowsIdentity.GetCurrent() return Costa identity (impersonated)
requestFields.CurrentPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
ThreadPool.QueueUserWorkItem(LogRequest, requestFields);

--

\\ Sub thread code that works
RequestFields requestFields = (RequestFields)obj;
HttpRequest httpRequest = requestFields.Request;

var impersonationContext = ((WindowsIdentity)requestFields.CurrentPrincipal.Identity).Impersonate();
.
.
.
impersonationContext.Undo();

回答1:

The reason that the worker thread isn't automatically impersonating the user from the request thread is that the .NET thread pool manages the creation and re-use of its threads. If you created the thread yourself via the example code below, I would expect it to inherit security context (including impersonation) automatically, but you also wouldn't be re-using threads which would add some execution cost (see Thread vs ThreadPool for details on the differences between the two and the benefits of using a thread pool thread).

Since as you know IIS does impersonate users and according to http://blogs.msdn.com/b/tmarq/archive/2007/07/21/asp-net-thread-usage-on-iis-7-0-and-6-0.aspx it uses the thread pool to process its requests, I would conclude that it is not dangerous to do impersonation on thread pool threads as long as you take steps to be as sure as possible that the impersonation will be undone even in exceptional circumstances. If the impersonation is not undone, you run the risk of other code that uses the thread pool (yours, other libraries, or the .NET framework itself) being assigned to a thread that is using some random identity instead of the application pool identity.

I'm not sure what the RequestFields class is (a quick search seems to indicate that it's not part of the .NET framework), so I don't understand why it is necessary to wrap the WindowsIdentity in a WindowsPrincipal since you don't use any properties other than Identity and it forces you to cast on the other thread. If you own this class and can change it, I'd recommend changing the CurrentPrincipal property to take the WindowsIdentity directly so it can be passed without the unnecessary WindowsPrincipal wrapper.

I think it's fine for you to pass the current WindowsIdentity to another thread pool thread and call Impersonate the way you're doing it. However, you should definitely either wrap impersonationContext in a using block or wrap the Undo in the finally part of a try/finally block that starts at the call to Impersonate to ensure that the impersonation is undone even in the event of an exception or thread abort. You should also make sure that the copy of the WindowsIdentity created by WindowsIdentity.GetCurrent() is disposed as well so all references to the unmanaged user token behind the identity are closed deterministically (i.e. not by garbage collector finalization).

Example code for creating a new thread:

Thread myThread = new Thread(LogRequest);

// the CLR will not wait for this thread to complete if all foreground threads
// have been terminated; this mimics the thread pool since all thread pool threads
//are designated as background threads
myThread.IsBackground = true;

myThread.Start(requestFields);

Example code for properly disposing of the WindowsImpersonationContext and WindowsIdentity objects with a using block (impersonationContext.Dispose will call Undo):

using (var identity = (WindowsIdentity)requestFields.CurrentPrincipal.Identity)
using (var impersonationContext = identity.Impersonate())
{
    .
    .
    .
}