You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core多租户应用Program.cs异常捕获方案咨询

优雅处理多租户租户验证异常的方案

你的问题核心是令牌验证阶段抛出的SecurityTokenInvalidIssuerException未被正确捕获,导致应用崩溃。令牌验证发生在请求处理流程的认证中间件阶段,而非应用启动阶段,你需要在认证流程或全局异常处理中捕获并处理该异常,以下是具体实现方案:

方案一:通过OpenIdConnect事件捕获异常

直接在OpenIdConnectOptions中配置OnAuthenticationFailed事件,精准捕获令牌验证异常,返回友好提示而非让应用崩溃:

修改Program.cs中的配置:

var builder = WebApplication.CreateBuilder(args);

// 添加身份认证服务
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration);

// 注入IConfiguration到UserValidator(替代手动构建配置的冗余操作)
builder.Services.AddScoped<UserValidator>();

// 配置OpenIdConnect选项
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    var userValidator = builder.Services.BuildServiceProvider().GetRequiredService<UserValidator>();
    options.TokenValidationParameters.IssuerValidator = userValidator.ValidateSpecificIssuers;

    // 捕获认证失败异常
    options.Events.OnAuthenticationFailed = context =>
    {
        if (context.Exception is SecurityTokenInvalidIssuerException)
        {
            // 返回自定义403响应
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            context.Response.ContentType = "text/html";
            return context.Response.WriteAsync("<h1>访问被拒绝</h1><p>你的账号所属租户未被授权访问此应用。</p>");
        }
        // 其他异常交给默认逻辑处理
        return Task.CompletedTask;
    };
});

var app = builder.Build();

// 确保异常处理中间件顺序正确(开发环境用开发者异常页,生产环境用自定义错误页)
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

同时优化UserValidator,通过依赖注入获取IConfiguration,避免重复构建配置实例:

public class UserValidator
{
    private readonly IConfiguration _configuration;

    // 构造函数注入IConfiguration
    public UserValidator(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public string ValidateSpecificIssuers(string issuer, SecurityToken securityToken,
                                          TokenValidationParameters validationParameters)
    {
        var validIssuers = GetAcceptedTenantIds()
                             .Select(tid => $"https://login.microsoftonline.com/{tid}/v2.0");
        if (validIssuers.Contains(issuer))
        {
            return issuer;
        }
        else
        {
            throw new SecurityTokenInvalidIssuerException("The sign-in user's account does not belong to one of the tenants that this Web App accepts users from.");
        }
    }

    private string[] GetAcceptedTenantIds()
    {
        // 直接通过注入的配置获取租户列表
        var acceptedTenants = _configuration.GetSection("Tenants").GetChildren();
        return acceptedTenants.Select(t => t.GetValue<string>("TenantId")).ToArray();
    }
}

方案二:全局异常处理中间件

如果需要统一处理所有未捕获的异常(包括令牌验证异常),可以自定义全局异常中间件:

  1. 创建异常处理中间件:
public class GlobalExceptionMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (SecurityTokenInvalidIssuerException ex)
        {
            context.Response.StatusCode = StatusCodes.Status403Forbidden;
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsJsonAsync(new
            {
                StatusCode = 403,
                Message = "你的账号所属租户未被授权访问此应用。"
            });
        }
        catch (Exception ex)
        {
            // 处理其他未知异常
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsJsonAsync(new
            {
                StatusCode = 500,
                Message = "服务器内部错误"
            });
        }
    }
}
  1. 在Program.cs中注册中间件(注意要放在认证中间件之前):
var app = builder.Build();

// 注册全局异常中间件
app.UseMiddleware<GlobalExceptionMiddleware>();

// 环境相关异常处理配置
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// 其他中间件配置...

关键说明

  • 令牌验证发生在请求处理流程的认证中间件阶段,而非应用启动阶段,因此无法在Program.cs的启动代码中直接捕获异常,必须通过认证事件或全局中间件处理。
  • 通过依赖注入IConfiguration替代手动构建配置实例,符合ASP.NET Core的设计规范,也便于后续扩展(比如从数据库动态读取允许的租户列表)。

内容的提问来源于stack exchange,提问作者brad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 13:01:09