问题
Before I explain my problem, here's our scenario:
Scenario
We write software only for our intranet Windows users (currently managed by local Active Directory but in future it is possible we migrate to Azure-AD).
Up to yet there is an old monolithic Winforms app which communicates directly with the database using datasets. All requests to the database happens with WindowsIdentity
(end-user context), so the database knows the end user.
For future development we want to use a Web API for business logic. Only the web-app should access the database with Entity Framework. We wrote a Web API with ASP.NET Core (stateless) which is hosted in IIS. Because the web app runs in the app pools identity, we wrote a middleware which impersonates the context to the end-user (in order that the database access is working).
While migrating to the web server both versions must be supported because we cannot migrate the whole application at once.
Problem
In debug environment the web server works fine (because there happens no impersonation) but on the live system sometimes the server crashes (not every time).
The most frequent error we get is FileLoadException
which cannot load dll's (mostly System.Reflection
). Other times the whole server runs without errors return 200 OK but doesn't contain any http body.
So it seems there is a problem. The documentation has a small hint to this:
https://docs.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.1#impersonation
So run the whole request in user context seems a bad idea but would could be the solution?
Of course all we have to discard is the impersonation middleware. But how access the database?
Option #1 : run each database call impersonated
Question
The official documentation says that code in the impersonation context is forbidden to run async things. We can do that, no problem. But I don't know if the Entity Framework runs async parts? If that would happen our app can crash again. Do you have any idea?
Question #2
If two requests come in asp would run two threads. Is it allowed to call impersonation at some time async on both reqeusts? The official documentation is not so clear.
Option #2: change our database so the end user is not required
Question
Some SQL triggers (and stored orocedures) use the user name for e.g. write the name into a table on insert. Of course we can change that behaviour so the EF Core write the name by hand.
This problem is more a political one to find reasons why we should change the working solution away from WindowsIdentity
to an AppIdentity
. Do you ahve some good arguments?
Option #3: do you have any more ideas?
I don't see more solutions, maybe you do?
By the way. Here is the code of our impersonation middleware:
public class ImpersonateMiddleware
{
private readonly RequestDelegate _next;
public ImpersonateMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var winIdent = context?.User?.Identity as WindowsIdentity;
if (winIdent == null)
{
await _next.Invoke(context).ConfigureAwait(true);
}
else
{
await WindowsIdentity.RunImpersonated(winIdent.AccessToken, async () =>
{
await _next.Invoke(context)
.ConfigureAwait(true)
;
}).ConfigureAwait(true);
}
}
}
回答1:
1.1 Question: The official documentation says that code in the impersonation context is forbidden to run async things. We can do that, no problem. But I don't know if the EntityFramework runs async parts? If that would happen our app can crash again. Have you any idea?
I actually think that this part of the documentation is outdated and/or wrong. There has been a request for an asynchronous overload for RunImpersonated
before and the issue mentions that passing asynchronous functions will work fine because the impersonation will use an async local internally.
So you could use RunImpersonatedAsync for this to be super explicit, or just keep using RunImpersonated because that’s literally doing the same thing. Using it for asynchronous work is also explicitly allowed by the docs:
This method may be used reliably with the async/await pattern, unlike
Impersonate
. In an async method, the generic overload of this method may be used with an async delegate argument so that the resulting task may be awaited.
I believe that the ASP.NET Core docs are just outdated on this and opened an issue for this to get it corrected or clarified.
Running the whole request impersonated should also work just fine, so unless I hear something else in that issue, I would assume that the crashes you are seeing in your application might have a different reason than just impersonation. I would suggest you to try to reproduce this separately using a different (non-development) environment with and without impersonation first to see if this is actually related to the impersonation. A FileLoadException at least does not sound like an issue with impersonation, except maybe that an impersonated user could not access some assemblies that are loaded late (I’m not sure if impersonation could have an impact here).
1.2 Question: If two requests come in asp would run two threads. Is it allowed to call impersonation at some time async on both requests? The official documentation is not so clear.
Since impersonation is implemented using an async local, the impersonation is restricted to a single local call flow. So a different request, using a different async flow, will not be impacted by the other being impersonated. This also makes the impersonation thread-safe.
2.1 Question: Some SQL-Trigger (and StoredProcedures) use the user name for e.g. write the name into a table on insert. Of course we can change that behaviour so the EFCore write the name by hand.
Yes, passing that username into your queries is probably the way to go.
This problem is more a political one to find reasons why we should change the working solution away from WindowsIdentity to an AppIdentity. Have you some arguments?
Having been involved in a project in the past where this was a requirement as well, I can say that impersonating down to the database didn’t really convince me. In order for this to work, each user needs to be authorized to make changes in the database. That also means that with direct database access, users could likely make changes that they wouldn’t be allowed to do just using the application. They could basically go around any application-specific authorization and protections.
Of course, users shouldn’t be able to connect directly to the database but this can always happen. And having no protections in place then makes this just a bit more critical. Also think about those with administrator access to your machines, e.g. the ones that are developing the application. Instead of having restricted access by not having an account that could make changes in the database, they do have access by default because that’s how the application works.
It’s a lot more difficult to protect your database in these situations compared to having a limited number of accounts with limited access that are only known to small number of users.
Also, since you mentioned migration to Azure AD, in order for your application to impersonate, your AD needs to be fully federated and the application server needs to be part of the domain. So you have to keep this in mind.
And, since you are still relying on Windows Authentication, this also makes authentication a bit more complicated. Users will have to be connected to the domain controller in order to benefit from NTLM, otherwise they will have to authenticate themselves using their account data every time. You won’t be able to switch to alternative authentication means, e.g. using Azure AD or ADFS, meaning that you also won’t benefit from things like Single-Sign-On.
(Note: You can use something other than Windows Authentication, and have the application impersonate the user by creating a Windows identity itself. But trust me, you don’t want to do that; it’s a configuration nightmare)
来源:https://stackoverflow.com/questions/65407501/impersonation-middleware-in-an-asp-net-core-intranet-app-for-windows-identity