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的请求头中。
- 首先在Program.cs中确保已启用令牌获取:
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(builder.Configuration) .EnableTokenAcquisitionToCallDownstreamApi() .AddMicrosoftIdentityWebApi(builder.Configuration) .AddInMemoryTokenCaches();
- 在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
相关产品推荐
相关产品推荐

