通过ASP.NET Core Blazor Web应用调用时,Web服务返回401(未授权)

后端开发 2026-07-11

我一直在按照 将ASP.NET Core Blazor Web应用与Microsoft Entra ID一起进行安全保护的指南 来操作,这看起来正是我需要的。

我的问题出现在在“MinimalApiJwt”中执行这行 app.MapGet("/weather-forecast", () =\> 时,出现了“HTTP 401 - Unauthorized”的错误。

移除 .RequireAuthorization() 显示该函数在没有这个检查的情况下可以运行,而将 (ClaimsPrincipal user) 作为DI值添加后,user.Identity.IsAuthenticated 是false。

我的起点是来自GitHub的这段代码 this code,与示例相比改动非常小,请注意我使用的是 "Microsoft Entra ID" 而不是 "Microsoft Entra External ID"。

我的修改 - 整体方案:

  • 更新为使用 .slnx 文件格式
  • 更新NuGet包

项目 BlazorWebAppEntra (program.cs):

  • 新增了行 msIdentityOptions.ClientSecret = SecretValue;
  • 创建了一些变量,通过新的 ReadConfig 函数进行设置
  • 根据Entra ID的注释替换占位符

项目 MinimalApiJwt (program.cs):

  • 注释掉 .RequireAuthorization(),因为我遇到了HTTP 401错误
  • 新增DI (ClaimsPrincipal user),以便检查用户详细信息
  • 创建了一些变量,通过新的 ReadConfig 函数进行设置
  • 根据Entra ID的注释替换占位符

来自 BlazorWebAppEntra (Program.cs) 的代码:

using System.Security.Claims;
using Azure.Core;
using Azure.Identity;
using BlazorWebAppEntra.Client.Weather;
using BlazorWebAppEntra.Components;
using BlazorWebAppEntra.Weather;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders.Distributed;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents()
    .AddAuthenticationStateSerialization(options => options.SerializeAllClaims = true);

string ReadConfig(string key) => builder.Configuration[$"AzureAd:{key}"] ?? throw new Exception($"{key} not set");

string TenantID = ReadConfig("TenantId");
string ClientId = ReadConfig("ClientId");
string WebAPIAppClientID = ReadConfig("WebAPIAppClientID");
string WebAPIAppClientBaseURL = ReadConfig("WebAPIAppClientBaseURL");
string SecretValue = ReadConfig("SecretValue");
string Domain = ReadConfig("Domain");

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = ClientId;
        msIdentityOptions.Domain = Domain;
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = TenantID;
        msIdentityOptions.ClientSecret = SecretValue;
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = WebAPIAppClientBaseURL;
        configOptions.Scopes = [ WebAPIAppClientID + "/Weather.Get" ];
    })
    .AddDistributedTokenCaches();


builder.Services.AddDistributedMemoryCache();

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(
    options =>
    {
        options.Encrypt = true;       
    });


builder.Services.AddAuthorization();

builder.Services.AddScoped<IWeatherForecaster, ServerWeatherForecaster>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
    app.UseWebAssemblyDebugging();
else
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();

app.MapGet("/weather-forecast", ([FromServices] IWeatherForecaster WeatherForecaster) =>
{
    return WeatherForecaster.GetWeatherForecastAsync();
}).RequireAuthorization();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(BlazorWebAppEntra.Client._Imports).Assembly);

app.MapGroup("/authentication").MapLoginAndLogout();

app.Run();

来自 MinimalApiJwt (Program.cs) 的代码:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.Extensions.Options;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

string ReadConfig(string key) => builder.Configuration[$"AzureAd:{key}"] ?? throw new Exception($"{key} not set");
string TenantID = ReadConfig("TenantId");
string WebAPIAppClientID = ReadConfig("WebAPIAppClientID");

builder.Services.AddAuthentication()
    .AddJwtBearer("Bearer", jwtOptions =>
    {
        jwtOptions.Authority = "https://sts.windows.net/" + TenantID;
        jwtOptions.Audience = "api://" + WebAPIAppClientID;
    });

builder.Services.AddAuthorization();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(); // Add NSwag services

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseOpenApi();
    app.UseSwaggerUi();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weather-forecast", (ClaimsPrincipal user) =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
}); //.RequireAuthorization();

app.Run();

internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Entra ID管理中心

我没有直接访问权限,因为这是由网络管理员设置的,但如果需要任何细节,请告诉我,我可以请他们提供。

先行致谢,感谢您的帮助

解决方案

首先,看来你的JWT授权机构设置不正确。目前,你使用的是:“https://sts.windows.net”,这对Entra ID v2无效。

你应该将代码改为:

jwtOptions.Authority = "https://login.microsoftonline.com/{tenantId}/v2.0";

请确保将 {tenantId} 替换为Entra的实际TenantId。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章