ASP.NET Core MVC 基于请求标识的不同路由单租户+同站多租户

问题描述

在任何人说任何话之前,我知道这是一个相当荒谬的要求,但这是客户想要的。

背景

我们有一个用于数据管理的棕地系统,允许两种不同类型的用户进行访问,我们将其称为 TenantAdmin

  • Tenant 用户只能访问自己的数据,并从其身份中的 TenantContext 声明中获取 TenantId
  • Admin 用户可以访问所有 Tenants 的所有数据,以及 少数管理员只查看,并通过会话状态驱动的机制获取特定 TenantContextTenant
  • 还有一些不需要 TenantContext(通常是静态信息等)的视图可以被两种类型的用户访问,有些也可以被匿名用户访问。

我们希望摆脱这种有状态实现,转而采用完全无状态系统。

问题

我们面临的问题是如何识别 TenantContext 用户在哪个 Admin 中工作,而无需将其存储在会话状态中。显而易见的解决方案是在所有需要 TenantContext 的网址前加上 TenantId,例如 https://example.com/{tenantId}/somecontroller/someaction,但是这样做当然也会影响 Tenant 用户的网址,他们目前不知道或需要知道他们的 TenantId,因为 Tenant 用户只能分配给一个 TenantId。不幸的是,客户已经明确表示这是不可接受的。

所以需要回答的问题是,“如何根据当前提出请求的用户构建一个既是单租户又是多租户的网站?” >

我的第一个想法转向了一些 url 重写中间件,我已经实现了半成功:

public enum UserRole
{
    Tenant,Admin
}

public class IdentityBasedMultiTenantMiddleware
{
    private readonly RequestDelegate _next;

    public IdentityBasedMultiTenantMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        if (!(httpContext.User?.Identity?.IsAuthenticated ?? false))
        {
            await _next(httpContext);
            return;
        }

        if (!Enum.TryParse<UserRole>(httpContext.User.FindFirst("Role")?.Value,out var role))
        {
            await _next(httpContext);
            return;
        }

        if (role != UserRole.Admin)
        {
            await _next(httpContext);
            return;
        }

        if (httpContext.Request.Path.Value == "/"
            || httpContext.Request.Path.Value.StartsWith("/admin"))
        {
            await _next(httpContext);
            return;
        }

        var pathSegments = httpContext.Request.Path.Value.Split("/",StringSplitOptions.RemoveEmptyEntries);
            
        if (!Guid.TryParse(pathSegments.FirstOrDefault(),out var tenantId))
        {
            httpContext.Response.Redirect("/admin/tenantsearch");
            return;
        }

        httpContext.Items.Add("TenantId",tenantId);
        httpContext.Request.Path = httpContext.Request.Path.Value.Remove(0,tenantId.ToString().Length + 1);
        httpContext.Request.PathBase = $"/{tenantId}";

        await _next(httpContext);
    }
}

注意。我将 tenantId 添加httpContext.Items 集合中是为了演示这个概念。

但是,这种方法存在问题:

  • 如您所见,我不得不为所有管理路由添加 /admin 前缀,并硬编码一个条件来处理此问题。所有不需要 TenantContext 的视图也需要它,从而为管理不需要 TenantContext 的任何路由留下相当讨厌的维护开销。
  • 当以 Admin 用户身份查看任何页面时,一旦通过将 TenantContext 添加到 url(例如 https://example.com/{tenantId}/somecontroller/someaction)来建立 TenantContext,因为 url 重写的工作方式,整个网站的所有链接(不仅仅是需要 {tenantId}链接)都以 IUrlHelperFactory 为前缀 - 虽然我怀疑这个特定问题可以通过 TenantContext自定义实现来解决,但是当然,还需要镜像相同的条件,并为管理页面等带来相同的维护开销。

然后我继续解决这个问题,同时仍然使用 url 重写。如果需要 TenantId 的路由总是包含 Tenant,而我们只为 TenantContext 用户重写这些路由怎么办?不幸的是,这与第一个 url 重写解决方案产生了相同的问题,因为需要在中间件中识别需要 HttpContext 的路由和条件来处理它们。

最终我认为这是 url 重写方法一个限制,因为我们在 Routing 级别而不是 [Route("someroute")] 级别工作,在那里我们可能有更多关于做出决定的映射控制器/动作。

我继续研究如何使用 MVC 路由引擎实现这一点,但我看不到前进的方向 - 这里有什么我遗漏的吗?也许动态控制器路由?所有的想法将不胜感激。

其他几个问题:

  • 我们对现有网站混合使用属性路由 ([HttpGet("someroute")]/TenantId) 和基于约定的路由。我们正在转向纯粹基于属性的路由。
  • 我们还研究了构建一个标签助手,将包含 Admin查询字符串附加到 const Tododata = [ { id: 3,ext: 'to take a shower',completed: true },{ id: 5,ext: 'to brush teeth',... ]; 用户链接 - 这种方法有效,但我认为它很脏,难以维护(容易破坏) 并最终以错误的方式实现这一目标。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...