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

Blazor Server基于Azure AD OIDC调用API控制器的身份验证问题

Blazor Server调用同项目API控制器的身份认证解决方案

问题背景

我有一个Blazor Server Web应用,通过OIDC使用Azure AD在Razor组件内进行授权访问,这部分运行正常,登录后可以在所有Razor组件中访问User ClaimsPrincipal。但应用的业务逻辑都在同项目的控制器中,Razor组件通过HTTP调用这些控制器时,无法获取用户声明的JWT令牌放入Authorization头,也不确定是否可以无需传递令牌让控制器通过HttpContext访问用户声明。

相关代码如下:

DevicesController.cs

[Route("api/[controller]")]
[ApiController]
[Authorize(Roles = "Administrator", "User")]
public class DevicesController : ControllerBase
{
    private readonly ILogger<DevicesController> _logger;
    private readonly AppSettings _config;
    private readonly IDeviceEnvironmentService _deviceEnvironmentService;

    public DevicesController(ILogger<DevicesController> logger, IOptions<AppSettings> config, IDeviceEnvironmentService deviceEnvironmentService,)
    {
        _logger = logger;
        _config = config.Value;
        _deviceEnvironmentService = deviceEnvironmentService;
    }

    [HttpGet]
    public async Task<ActionResult<object>> Index()
    {
        try
        {
            return await _deviceEnvironmentService.GetEnvironmentDevices(_config.Environment);
        }
        catch(Exception ex)
        {
            _logger.LogError(ex, $"Failed to fetch devices. Exception details: {ex}");
            return StatusCode(500, "Failed to fetch devices.");
        }
    }
}

DeviceIndex.razor

@code {
    [CascadingParameter] Task<AuthenticationState> AuthenticationStateTask { get; set; }

    private ClaimsPrincipal User { get; set; }
    private List<DeviceEnvironment> devices { get; set; }
    private HubConnection hubConnection;  

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateTask;
        User = authState.User;

        hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/DeviceHub"))
        .Build();

        hubConnection.On("ReceiveMessage", () =>
        {
            LoadData();
            StateHasChanged();
        });

        await hubConnection.StartAsync();
        LoadData();
    }

    public bool IsConnected => hubConnection.State == HubConnectionState.Connected;

    protected async void LoadData()
    {
        devices = await Http.GetFromJsonAsync<List<DeviceEnvironment>>($"{NavigationManager.BaseUri}api/devices");
        StateHasChanged();
    }
}

Program.cs

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration);
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration);

builder.Services.AddControllersWithViews(options =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    })
    .AddMicrosoftIdentityUI();

builder.Services.AddAuthorization(options =>
{
    // By default, all incoming requests will be authorized according to the default policy
    options.FallbackPolicy = options.DefaultPolicy;
});

builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor()
    .AddMicrosoftIdentityConsentHandler();

var app = builder.Build();

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

app.Run();

解决方案

方案一:无需传递令牌,让控制器直接通过HttpContext获取用户声明

你的Program.cs中存在重复注册AddAuthentication的问题,这会导致认证中间件冲突。可以合并配置,让Blazor Server同时支持Web App和Web Api的认证,这样API控制器就能直接通过HttpContext获取登录用户的声明,无需手动传递令牌。

修改Program.cs的认证注册部分:

// 合并Web App和Web Api的认证配置
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration)
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftIdentityWebApi(builder.Configuration)
    .AddInMemoryTokenCaches();

// 配置API控制器的授权策略,允许使用Cookie认证(Blazor Server默认使用Cookie)
builder.Services.AddControllersWithViews(options =>
{
    var policy = new AuthorizationPolicyBuilder(
        OpenIdConnectDefaults.AuthenticationScheme,
        JwtBearerDefaults.AuthenticationScheme)
        .RequireAuthenticatedUser()
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
})
.AddMicrosoftIdentityUI();

修改后,API控制器的User属性就能直接获取当前登录用户的ClaimsPrincipal,和Blazor组件中的用户一致,无需额外处理。

方案二:获取JWT令牌并通过Authorization头传递

如果确实需要通过JWT令牌调用API,可以使用ITokenAcquisition服务获取访问令牌,然后添加到HttpClient的请求头中。

  1. 首先在Program.cs中确保已启用令牌获取:
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration)
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftIdentityWebApi(builder.Configuration)
    .AddInMemoryTokenCaches();
  1. 在Razor组件中注入ITokenAcquisition服务,并修改LoadData方法:
@inject ITokenAcquisition TokenAcquisition

@code {
    [CascadingParameter] Task<AuthenticationState> AuthenticationStateTask { get; set; }

    private ClaimsPrincipal User { get; set; }
    private List<DeviceEnvironment> devices { get; set; }
    private HubConnection hubConnection;  

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateTask;
        User = authState.User;

        hubConnection = new HubConnectionBuilder()
        .WithUrl(NavigationManager.ToAbsoluteUri("/DeviceHub"))
        .Build();

        hubConnection.On("ReceiveMessage", () =>
        {
            LoadData();
            StateHasChanged();
        });

        await hubConnection.StartAsync();
        await LoadData(); // 改为async Task,避免async void的问题
    }

    public bool IsConnected => hubConnection.State == HubConnectionState.Connected;

    protected async Task LoadData() // 改为async Task
    {
        // 获取访问令牌,替换为你的API范围(可以在appsettings.json中配置)
        var accessToken = await TokenAcquisition.GetAccessTokenForUserAsync(new[] { "api://your-api-client-id/access_as_user" });
        
        // 创建HttpClient请求并添加Authorization头
        var requestMessage = new HttpRequestMessage(HttpMethod.Get, $"{NavigationManager.BaseUri}api/devices");
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        
        var response = await Http.SendAsync(requestMessage);
        if (response.IsSuccessStatusCode)
        {
            devices = await response.Content.ReadFromJsonAsync<List<DeviceEnvironment>>();
        }
        else
        {
            // 处理错误
        }
        StateHasChanged();
    }
}

注意:需要在appsettings.json中配置正确的API范围和客户端ID,确保Azure AD应用注册中已添加对应的API权限。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 20:57:19