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

如何测试基于Azure AD认证的Hangfire仪表盘?401问题排查

Hangfire仪表盘Azure B2C认证401问题排查与解决

问题背景

参考《.NET Core WebApi Secure Hangfire Dashboard with Azure AD Login》实现Hangfire仪表盘的Azure AD认证,前端采用Azure B2C登录,Hangfire托管于后端API中。将前端通过MSAL获取的Bearer token附加到请求头后,访问仪表盘仍返回401 Unauthorized。

当前配置代码

认证扩展方法

public static void AddB2CAuthorisation(this IServiceCollection services, IConfiguration configuration)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(configuration.GetSection("AzureAdB2C"));
}

public static void AddHangfireAuthentication(this IServiceCollection services, IConfiguration configuration)
{
    services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApp(configuration.GetSection("AzureAd"));
    
    services.AddAuthorization(options =>
    {
        options.AddPolicy("HangfireAzureAdPolicy", builder =>
        {
            builder
                .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
                .RequireAuthenticatedUser();
        });
    });
}

public static void AddHangfire(this IServiceCollection services, IConfiguration configuration)
{
    services.AddHangfire(config => config
        .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
        .UseSimpleAssemblyNameTypeSerializer()
        .UseRecommendedSerializerSettings()
        .UseSqlServerStorage(configuration.GetConnectionString("MyDBConnectionString")));
    services.AddHangfireServer();
}

Program.cs配置

app.UseHangfireDashboard();
app.UseEndpoints(endpoints =>
{
    endpoints.AddHangfireRoute(); // rest of this is below
    endpoints.MapRazorPages();
    endpoints.MapControllers();
});

public static IEndpointRouteBuilder AddHangfireRoute(this IEndpointRouteBuilder endpoints)
{
    endpoints.MapHangfireDashboard("/hangfire", new DashboardOptions
        {
            DashboardTitle = "Hangfire Dashboard Name",
            Authorization = new []
            {
                new HangfireDashboardAuthorizationFilter()
            }
        })
        .RequireAuthorization("HangfireAzureAdPolicy"); // Matches your policy name in Auth
    return endpoints;
}

自定义授权过滤器

public class HangfireDashboardAuthorizationFilter : IDashboardAuthorizationFilter
{
    public bool Authorize([NotNull] DashboardContext context)
    {
        var httpContext = context.GetHttpContext();
        return httpContext.User.Identity.IsAuthenticated;
    }
}

应用配置

{
  "AzureAdB2C": {
    "Instance": "",
    "Domain": "",
    "ClientId": "",
    "SignUpSignInPolicyId": ""
  },
  "AzureAd": {
    "Instance": "",
    "Domain": "",
    "ClientId": "",
    "TenantId": ""
  }
}

核心问题分析

  1. 重复调用AddAuthentication导致配置覆盖:两次调用AddAuthentication会替换默认认证方案和之前的配置,导致Azure B2C的JwtBearer认证配置被覆盖,无法正确验证前端传入的B2C token。
  2. 中间件顺序错误:未确保UseAuthentication()和UseAuthorization()在Hangfire相关中间件之前执行,认证逻辑不会生效。
  3. Hangfire路由重复配置:同时调用app.UseHangfireDashboard()和endpoints.MapHangfireDashboard,导致路由冲突,且前者未配置授权策略。
  4. Token有效性验证缺失:未确认前端获取的token受众(Audience)是否匹配API的Azure B2C应用ClientId,以及Issuer是否与配置一致。

解决方案

1. 合并认证配置,避免覆盖

修改认证扩展方法,合并JwtBearer(B2C)和OpenIdConnect(Azure AD)的配置,只调用一次AddAuthentication:

public static void AddAuthConfigurations(this IServiceCollection services, IConfiguration configuration)
{
    // 默认采用JwtBearer方案,用于验证前端传入的B2C Bearer token
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        // 添加Azure B2C的JWT认证支持,用于API和Hangfire的token验证
        .AddMicrosoftIdentityWebApi(configuration.GetSection("AzureAdB2C"))
        // 可选:添加Azure AD的OpenIdConnect支持,用于浏览器直接访问仪表盘时跳转到Azure AD登录
        .AddMicrosoftIdentityWebApp(configuration.GetSection("AzureAd"));

    services.AddAuthorization(options =>
    {
        options.AddPolicy("HangfireAzureAdPolicy", builder =>
        {
            builder
                .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
                .RequireAuthenticatedUser();
        });
    });
}

2. 修正中间件顺序

确保认证中间件在Hangfire和端点配置之前执行,同时删除重复的Hangfire路由配置:

var app = builder.Build();

// 其他中间件(如HttpsRedirection、StaticFiles等)
app.UseHttpsRedirection();

// 必须先启用认证和授权中间件
app.UseAuthentication();
app.UseAuthorization();

// 删除重复的app.UseHangfireDashboard(),仅保留端点配置中的路由
app.UseEndpoints(endpoints =>
{
    endpoints.AddHangfireRoute();
    endpoints.MapRazorPages();
    endpoints.MapControllers();
});

3. 简化Hangfire授权配置

删除冗余的自定义授权过滤器(授权策略已包含认证检查):

public static IEndpointRouteBuilder AddHangfireRoute(this IEndpointRouteBuilder endpoints)
{
    endpoints.MapHangfireDashboard("/hangfire", new DashboardOptions
        {
            DashboardTitle = "Hangfire Dashboard Name"
            // 移除自定义Authorization过滤器,依赖RequireAuthorization策略即可
        })
        .RequireAuthorization("HangfireAzureAdPolicy");
    return endpoints;
}

4. 验证Token有效性

  • 确认前端获取的token的aud(受众)字段等于AzureAdB2C.ClientId(API的B2C应用注册ClientId)。
  • 确认token的iss(签发者)字段与配置的AzureAdB2C.Instance + AzureAdB2C.Domain + "/" + AzureAdB2C.SignUpSignInPolicyId + "/v2.0/"一致(例如https://your-b2c-domain/tfp/your-tenant-id/B2C_1_SignUpSignIn/v2.0/)。
  • 使用JWT解析工具检查上述字段是否匹配配置。

5. 配置正确的Azure B2C实例地址

确保AzureAdB2C.Instance的格式正确,应为https://{your-b2c-domain}/tfp/(例如https://contoso.b2clogin.com/tfp/),而非普通Azure AD的实例地址。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 00:08:15