用于 Windows-Identity 的 Asp.Net Core Intranet 应用中的模拟中间件

问题描述

在我解释我的问题之前,这是我们的场景:

场景

我们仅为内部网 Windows 用户编写软件(目前由本地 Active Directory 管理,但将来可能迁移到 Azure-AD)。

到目前为止,还有一个旧的整体式 Winforms 应用程序,它使用数据集直接与数据库通信。对数据库的所有请求都发生在 WindowsIdentity(最终用户上下文)中,因此数据库知道最终用户

对于未来的开发,我们希望使用 Web API 来处理业务逻辑。只有 Web 应用程序应该使用实体框架访问数据库。我们使用托管在 IIS 中的 ASP.NET Core(无状态)编写了一个 Web API。由于 Web 应用在应用池标识中运行,因此我们编写了一个中间件来模拟最终用户的上下文(以便数据库访问正常工作)。

在迁移到 Web 服务器时,必须支持两个版本,因为我们无法一次迁移整个应用程序。

问题

在调试环境中,Web 服务器工作正常(因为不会发生模拟),但在实时系统上,服务器有时会崩溃(并非每次都如此)。

我们得到的最常见的错误FileLoadException,它无法加载 dll(主要是 System.Reflection)。其他时候整个服务器运行没有错误返回 200 OK 但不包含任何 http 正文。

所以看起来有问题。文档对此有一个提示

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-3.1#impersonation

enter image description here

因此在用户上下文中运行整个请求似乎是一个坏主意,但可能是解决方案吗?

当然,我们必须放弃的是模拟中间件。但是如何访问数据库

选项 #1:运行每个模拟的数据库调用

问题

官方文档说模拟上下文中的代码禁止运行异步事物。我们可以做到,没问题。但我不知道实体框架是否运行异步部分?如果发生这种情况,我们的应用程序可能会再次崩溃。你有什么想法吗?

问题 2

如果有两个请求传入,asp 会运行两个线程。是否允许在某个时间在两个请求上异步调用模拟?官方文档不是很清楚。

选项 #2:更改我们的数据库,以便不需要最终用户

问题

一些 sql 触发器(和存储的 orocedures)使用用户名,例如在插入时将名称写入表中。当然,我们可以更改该行为,以便 EF Core 手动写入名称

这个问题更像是一个政治问题,需要找出我们应该将工作解决方案从 WindowsIdentity 更改为 AppIdentity 的原因。你有什么好的论据吗?

选项 #3:您还有其他想法吗?

我没有看到更多的解决方案,也许你有?


顺便说一下。这是我们的模拟中间件的代码

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 问题: 官方文档说模拟上下文中的代码禁止运行异步事物。我们可以做到,没问题。但我不知道 EntityFramework 是否运行异步部分?如果发生这种情况,我们的应用程序可能会再次崩溃。你有什么想法吗?

我实际上认为文档的这一部分已经过时和/或错误。之前曾有过对 RunImpersonated 异步重载的请求,the issue 提到传递异步函数可以正常工作,因为模拟将在内部使用异步本地。

所以你可以使用 RunImpersonatedAsync 来表示超级明确,或者继续使用 RunImpersonated 因为这实际上是在做同样的事情。文档也明确允许将其用于异步工作:

Impersonate 不同,此方法可以可靠地与 async/await 模式一起使用。在异步方法中,此方法的通用重载可以与异步委托参数一起使用,以便可以等待结果任务。

我相信 ASP.NET Core 文档在这方面已经过时了,opened an issue for this 需要对其进行更正或澄清。

运行模拟的整个请求也应该可以正常工作,因此除非我听到该问题中的其他内容,否则我会假设您在应用程序中看到的崩溃可能有与模拟不同的原因。我建议您首先尝试使用不同的(非开发)环境分别使用模拟和不模拟来重现这个,看看这是否真的与模拟有关。 FileLoadException 至少听起来不像是模拟问题,除非被模拟的用户可能无法访问某些延迟加载的程序集(我不确定模拟是否会在这里产生影响)。


1.2 问题: 如果有两个请求传入 asp 会运行两个线程。是否允许在某个时间对两个请求异步调用模拟?官方文档不是很清楚。

由于模拟是使用异步本地实现的,因此模拟仅限于单个本地调用流程。因此,使用不同异步流的不同请求不会受到被模拟的另一个请求的影响。这也使模拟线程安全。


2.1 问题: 一些 SQL 触发器(和存储过程)使用用户名,例如在插入时将名称写入表中。当然,我们可以更改该行为,以便 EFCore 手动写入名称。

是的,将该用户名传递到您的查询中可能是可行的方法。

这个问题更像是一个政治问题,以找出我们应该将工作解决方案从 WindowsIdentity 更改为 AppIdentity 的原因。你有什么争论吗?

过去参与过一个项目,这也是一个要求,我可以说模仿数据库并没有真正说服我。为了使其工作,每个用户都需要被授权在数据库中进行更改。这也意味着通过直接访问数据库,用户可能会进行更改,而这些更改仅使用应用程序是不允许的。他们基本上可以绕过任何特定于应用程序的授权和保护。

当然,用户不应该能够直接连接到数据库,但这总是可能发生的。如果没有适当的保护措施,那么这会变得更加重要。还要考虑那些对您的机器具有管理员访问权限的人,例如那些正在开发应用程序的人。由于没有可以在数据库中进行更改的帐户,因此他们没有限制访问权限,默认情况下他们确实有访问权限,因为这就是应用程序的工作方式。

在这些情况下保护您的数据库比拥有有限数量且只有少数用户知道的有限访问权限要困难得多。

此外,由于您提到迁移到 Azure AD,为了让您的应用程序能够模拟,您的 AD 需要完全联合并且应用程序服务器需要成为域的一部分。所以你必须牢记这一点。

而且,由于您仍然依赖 Windows 身份验证,这也使身份验证变得更加复杂。用户必须连接到域控制器才能从 NTLM 中受益,否则他们每次都必须使用他们的帐户数据进行身份验证。您将无法切换到其他身份验证方式,例如使用 Azure AD 或 ADFS,这意味着您也不会从单点登录等功能中受益。

(注意:您可以使用 Windows 身份验证以外的其他方法,并让应用程序通过创建 Windows 身份本身来模拟用户。但是相信我,您不想这样做那个;这是一个配置噩梦)