如何测试基于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": "" } }
核心问题分析
- 重复调用AddAuthentication导致配置覆盖:两次调用
AddAuthentication会替换默认认证方案和之前的配置,导致Azure B2C的JwtBearer认证配置被覆盖,无法正确验证前端传入的B2C token。 - 中间件顺序错误:未确保
UseAuthentication()和UseAuthorization()在Hangfire相关中间件之前执行,认证逻辑不会生效。 - Hangfire路由重复配置:同时调用
app.UseHangfireDashboard()和endpoints.MapHangfireDashboard,导致路由冲突,且前者未配置授权策略。 - 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
相关产品推荐
相关产品推荐

