验证多租户 .Net Core Web API 应用程序的发行者的正确位置

问题描述 投票:0回答:3

我正在开发一个多租户 SPA 应用程序,该应用程序调用后端 .Net Core Web API 来获取数据。前端 UI 将使用 MSAL 和 Microsoft 的 v2 公共端点根据 AAD 对用户进行身份验证并获取 ID 和访问令牌。

在我的 Web API 中,我想验证颁发者,但如此处所述,使用通用端点提供的元数据使正常的颁发者验证无法使用。

我已经看到了对几个地方的参考,在这些地方可以覆盖或自定义令牌验证,但我不确定哪个是首选,或者这些方法中的任何一个是否会导致不良的副作用。

一种方法使用 JwtBearer 选项的事件:

options.Events.TokenValidated
,另一种方法使用 TokenValidationParameters 的
IssuerValidator
委托。

除了确保发行者存在于经过验证的发行者数据库中之外,我不想编写任何令牌验证逻辑。该逻辑应该放在

IssuerValidator
还是
TokenValidated
中?

我当前的代码如下所示(当前为单个租户设置)

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
   .AddJwtBearer(options =>
    {
       options.Authority = "https://myauthority.com";
       options.Audience = "https://myaudience.com/api/v1";
       options.TokenValidationParameters = new TokenValidationParameters
       {  
          ValidateIssuer = true,
          ValidIssuer = "myauthority.com",
          ValidateAudience = true,
          ValidAudience = "https://myaudience.com",
          ValidateLifetime = true,
          ValidateIssuerSigningKey = true,
        };
    });

我在使用

IssuerValidator
时看到的问题之一是,似乎没有一种方法可以注入或传递对 dbContext 的引用,以便能够在数据库中查找租户 ID。

有人解决过这个问题或做过类似的事情吗?

c# single-page-application asp.net-core-webapi restful-authentication msal.js
3个回答
2
投票

您可以在

OnTokenValidated
事件中检查,要访问数据库上下文,您可以尝试:

 options.Events.OnTokenValidated = async (context) =>
   {


       var dbContext = context.HttpContext.RequestServices.GetRequiredService<BloggingContext>();
       var blogs = await dbContext.Blogs.ToListAsync();

       if (!true)
       {
           throw new SecurityTokenValidationException($"Tenant xxxxx is not registered");
       }

   };

2
投票

哇,这让我走上了很长的路!正如您所指出的,大多数文档都指向设置

ValidateIssuer = false
并就此离开。我尝试了
IssuerValidator
,但没有取得任何进展。我发现的是
IAuthorizationHandler
。我使用 IMyService 代替 DBContext 创建了一个 PoC。我离开了
ValidateIssuer = false

public class IssuerAuthorizationHandler : IAuthorizationHandler
{
    private readonly IMyService _service;

    public IssuerAuthorizationHandler(IMyService service)
    {
        _service = service ?? throw new ArgumentNullException(nameof(service));
    }

    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        if (context.User.FindFirst("iss") != null)
        {
            string issuer = context.User.FindFirst("iss").Issuer;
            // do issuer validation here
        }
        else
        {
            // fail the authentication
            context.Fail();
        }

        return Task.CompletedTask;
    }
}

将此添加到 DI

services.AddScoped<IAuthorizationHandler, IssuerAuthorizationHandler>();

希望这有帮助

更新:

显示何时调用授权处理程序的过滤器管道


0
投票

我的解决方案基于 matt_lethargic 的答案,但我想抛出一个特定的响应并控制发生的事情,而不是一揽子授权失败重定向。

Program.cs
中,我添加了一个 AuthorizationHandler 来检查租户,以及一个 MiddlewareResultHandler 来执行重定向。

// Check for approved Tenant
builder.Services.AddScoped<IAuthorizationHandler, ApprovedTenantRequirementHandler>();
builder.Services.AddScoped<IAuthorizationMiddlewareResultHandler, ApprovedTenantAuthorizationMiddlewareResultHandler>();

还在

Program.cs
中,我设置了默认策略以要求获得批准的租户。

builder.Services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .AddRequirements(new ApprovedTenantRequirement())
        .Build();
});

需求本身就很枯燥

ApprovedTenantRequirement.cs

public class ApprovedTenantRequirement : IAuthorizationRequirement
{
}

这是我们在数据库中检查租户 ID 的地方

ApprovedTenantRequirementHandler.cs

public class ApprovedTenantRequirementHandler : AuthorizationHandler<ApprovedTenantRequirement>
{
    private readonly ApplicationContext _applicationContext;

    public ApprovedTenantRequirementHandler(ApplicationContext applicationContext)
    {
        _applicationContext = applicationContext;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ApprovedTenantRequirement requirement)
    {
        var validTenantIds = await _applicationContext.Tenants.Select(t => t.TenantIdGuid).ToListAsync();
        var tenantIdClaim = context.User.FindFirst(c => c.Type == "tid");

        if (tenantIdClaim is null)
        {
            return; // Requirement wasn't met - handle this later
        }

        if (validTenantIds.Contains(tenantIdClaim.Value))
        {
            context.Succeed(requirement); // Happy days
        }
    }
}

中间件结果处理程序采用具有特定失败要求的失败策略并返回 403。我正在构建一个 api,因此我返回一些 JSON 以及一条解释所发生情况的消息。前端会预料到这一点并将其显示给用户。这意味着我们可以为授权失败提供一致的用户体验,即使失败的原因各不相同。

public class ApprovedTenantAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
    private readonly AuthorizationMiddlewareResultHandler defaultHandler = new();

    public async Task HandleAsync(
        RequestDelegate next,
        HttpContext context,
        AuthorizationPolicy policy,
        PolicyAuthorizationResult authorizeResult)
    {
        // If the authorization was forbidden and the resource had an approved tenant requirement,
        // provide a custom 403 response.
        if (authorizeResult.Forbidden && authorizeResult.AuthorizationFailure!.FailedRequirements
            .OfType<ApprovedTenantRequirement>().Any())
        {
            // Return a 403 and let them know why they have been denied.
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            await context.Response.WriteAsJsonAsync(new {message = "Your organisation has not been approved to access this application;"});
            return;
        }

        // Fall back to the default implementation.
        await defaultHandler.HandleAsync(next, context, policy, authorizeResult);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.