【问题标题】:Autofac multitenancy resolve tenant from current user’s principalAutofac 多租户从当前用户的主体解析租户
【发布时间】:2020-12-12 03:28:47
【问题描述】:

我有一个 .NET 5 ASP.NET Core API,我正在尝试使用 Autofac.AspNetCore.Multitenant v4.0.1 设置多租户。我已经实现了一个 TenantIdentificationStrategy,它从当前用户主体的声明中识别租户。 问题在于,在 Autofac 解析租户标识符时,用户似乎尚未填充。

此处的 Autofac 文档 https://autofaccn.readthedocs.io/en/latest/advanced/multitenant.html#tenant-identification 状态:

ITenantIdentificationStrategy 允许您检索租户 适合您的应用程序的任何位置的 ID:环境 变量,当前用户主体上的角色,传入请求 价值,或其他任何地方。

由于这个声明,我认为这应该有效,所以我想知道我是否做错了什么,或者框架是否存在错误,或者文档是否说明了一些不受框架。

知道如何让它发挥作用吗?

在我的应用配置下方找到:

计划

private static IHostBuilder CreateHostBuilder(string[] args)
    => Host.CreateDefaultBuilder(args)
        .UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(Startup.ConfigureMultitenantContainer))
        .ConfigureWebHostDefaults(webHostBuilder =>
        {
            webHostBuilder.UseStartup<Startup>();
        });

启动

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            // [...]
        });

    services.AddControllers();

    services.AddAutofacMultitenantRequestServices();
}

public void ConfigureContainer(ContainerBuilder builder)
{
    builder.RegisterType<ClaimTenantIdentificationStrategy>()
        .As<ITenantIdentificationStrategy>()
        .SingleInstance();

    builder.RegisterType<SomeService>()
        .As<ISomeService>()
        .InstancePerTenant();
}

public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
    => new MultitenantContainer(container.Resolve<ITenantIdentificationStrategy>(), container);

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();
    
    app.UseAuthentication();
    app.UseAuthorization();
    
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

TenantIdentificationStrategy 实施

public class ClaimTenantIdentificationStrategy : ITenantIdentificationStrategy
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ClaimTenantIdentificationStrategy(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
    }

    public bool TryIdentifyTenant(out object tenantId)
    {
        tenantId = null;

        var claimsPrincipal = _httpContextAccessor.HttpContext?.User;
        var claimsIdentity = claimsPrincipal?.Identity;

        if (claimsIdentity != null && claimsIdentity.IsAuthenticated)
        {
            var identifier = claimsPrincipal.FindFirst("tenantId")?.Value;

            if (!string.IsNullOrEmpty(identifier))
                tenantId = identifier;
        }

        return tenantId != null;
    }
}

编辑:TenantIdentificationStrategy

public class ClaimTenantIdentificationStrategy : ITenantIdentificationStrategy
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ITenantStore _tenantStore;

    public ClaimTenantIdentificationStrategy(IHttpContextAccessor httpContextAccessor, ITenantStore tenantStore)
    {
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        _tenantStore = tenantStore ?? throw new ArgumentNullException(nameof(tenantStore));
    }

    public bool TryIdentifyTenant(out object tenantId)
    {
        var httpContext = _httpContextAccessor.HttpContext;

        tenantId = httpContext?.Items["TenantId"];

        if (tenantId != null)
            return true;

        var authorizationHeaderValue = httpContext?.Request?.Headers?[HeaderNames.Authorization];

        if (authorizationHeaderValue.HasValue && !StringValues.IsNullOrEmpty(authorizationHeaderValue.Value))
        {
            var authorizationHeader = AuthenticationHeaderValue.Parse(authorizationHeaderValue);

            var jwtTokenHandler = new JwtSecurityTokenHandler();
            var jwtToken = jwtTokenHandler.ReadJwtToken(authorizationHeader.Parameter);

            var tenantIdClaim = jwtToken.Claims
                .FirstOrDefault(x => x.Type == "tenantId");

            if (tenantIdClaim != null && _tenantStore.IsValidTenant(tenantIdClaim.Value))
            {
                tenantId = tenantIdClaim.Value;
                httpContext.Items["TenantId"] = tenantId;

                return true;
            }
        }

        return false;
    }
}

【问题讨论】:

    标签: asp.net-core asp.net-core-webapi autofac multi-tenant


    【解决方案1】:

    阅读 Autofac 文档时要考虑的一点是 Autofac 支持 任何 应用程序类型 - 控制台应用程序、Windows 服务、ASP.NET 应用程序等等。因此,当您看到租户可以来自用户的主体时……嗯,它可以。 您的应用当时是否准备好运行取决于您的应用。控制台可能会从当前执行的线程中获取主体,并且在应用运行时它已经存在。

    对于 ASP.NET / ASP.NET Core 应用,运行中间件来验证入站令牌,将该令牌转换为主体,并将该主体附加到当前请求。

    但是如果你想一想...中间件有需要从请求容器中解决的依赖关系,并且在多租户系统中,这意味着我们需要了解中间件中的租户第一件事管道,不要等到身份验证/授权位运行。

    这也是 ASP.NET classic 中的一个问题。这对 ASP.NET Core 来说并不新鲜。我自己的应用程序也成功了。

    您如何解决这个问题很大程度上取决于应用程序 - 您使用的是什么类型的令牌、验证令牌的成本是多少、您希望在安全方面承担多少风险等等。

    在我最近的一次磨合中,我们使用了签名(但未加密)的 JWT。其中一个声明 tid 包含租户 ID。我们所做的是决定对令牌进行一些手动工作 - 验证签名,然后直接从令牌中获取 tid 声明。没有更多的解析,没有其他验证(观众等)。它需要很快。然后,我们将租户 ID 缓存在 HttpContext.Items 中,这样我们就不必再次查找它,因此它可以在我们需要在身份验证中间件之前运行的租户 ID 的任何其他地方使用。

    从初始请求到身份验证中间件,有人可以让不同租户的管道执行,然后被身份验证中间件拒绝,这存在轻微风险。我们对此表示同意,因为它仍然很快就会拒绝未经授权的人。这对你来说可能合适,也可能不合适,所以你必须做出决定。

    但是,总而言之:由于在身份验证中间件运行之前需要租户 ID 的竞争条件,我们不得不想出一些别的办法。看来你也得想出别的办法了。

    【讨论】:

    • 感谢您的回复,我可能想知道。我根据您的 cmets 添加了一个潜在的解决方案。和你建议的一样吗?
    • 我不能承诺审查或批准您的租户 ID 策略,尤其是因为它涉及一些安全风险。我很抱歉我不能,但希望你没有被阻止。如果是这样,请接受我的回答,以便我为您提供帮助。
    猜你喜欢
    • 2021-03-20
    • 2019-12-15
    • 1970-01-01
    • 1970-01-01
    • 2018-05-07
    • 1970-01-01
    • 1970-01-01
    • 2012-09-24
    • 2020-11-27
    相关资源
    最近更新 更多