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

Blazor WebAssembly自定义JWT身份提供器下API控制器获取用户身份问题

Blazor WebAssembly调用API时无法传递JWT认证信息的解决方案

问题背景

我同时为Blazor Server和Blazor WebAssembly实现了自定义JWT AuthenticationStateProvider,在Blazor Server中可以正常通过AuthenticationState获取用户身份,但在Blazor WebAssembly中通过HttpClient调用服务器API时,控制器无法获取调用方的用户名。

相关代码

自定义JwtAuthenticationStateProvider

public class JwtAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly ISessionStorageService sessionStorageService;
    private ClaimsPrincipal anonymous = new ClaimsPrincipal(new ClaimsIdentity());

    public JwtAuthenticationStateProvider(ISessionStorageService sessionStorageService)
    {
        this.sessionStorageService = sessionStorageService;
    }

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        try
        {
            var userSession = await sessionStorageService.ReadEncryptedItemAsync<UserSession>("UserSession");
            if (userSession == null) return await Task.FromResult(new AuthenticationState(anonymous));
            var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
            {
                new Claim(ClaimTypes.Name, userSession.UserName),
                new Claim(ClaimTypes.Role, userSession.Role)
            }, "JwtAuth"));
            return await Task.FromResult(new AuthenticationState(claimsPrincipal));
        }
        catch (Exception ex)
        {
            // 可在此处记录异常
            return await Task.FromResult(new AuthenticationState(anonymous));
        }
    }

    public async Task UpdateAuthenticationState(UserSession? userSession)
    {
        ClaimsPrincipal claimsPrincipal;
        if (userSession != null)
        {
            claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
            {
                new Claim(ClaimTypes.Name, userSession.UserName),
                new Claim(ClaimTypes.Role, userSession.Role)
            }));
            userSession.ExpiryTimeStamp = DateTime.Now.AddSeconds(userSession.ExpiresIn);
            await sessionStorageService.SaveItemEncryptedAsync("UserSession", userSession);
        }
        else
        {
            claimsPrincipal = anonymous;
            await sessionStorageService.RemoveItemAsync("UserSession");
        }
        NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal)));
    }

    public async Task<string> GetToken()
    {
        var result = string.Empty;
        try
        {
            var userSession = await sessionStorageService.ReadEncryptedItemAsync<UserSession>("UserSession");
            if (userSession != null && DateTime.Now < userSession.ExpiryTimeStamp) result = userSession.Token;
        }
        catch (Exception ex)
        {
            // 可在此处记录异常
        }
        return result;
    }
}

服务器端API控制器

[Route("api/[controller]")]
[ApiController]
public class DevController : ControllerBase
{
    [HttpGet, Route("GetUserNameByUser")]
    public string GetUserNameByUser()
    {
        return User?.Identity?.Name ?? String.Empty;  // User?.Identity?.Name == null
    }


    [Microsoft.AspNetCore.Components.CascadingParameter] private Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState> AuthenticationState { get; set; }

    [HttpGet, Route("GetUserNameByAuthenticationState")]
    public async Task<ActionResult<string>> GetUserNameByAuthenticationState()
    {
        var currentUserName = (await AuthenticationState).User?.Identity?.Name;  // AuthenticationState == null
        return Ok(currentUserName);
    }
}

服务器端认证配置(Program.cs)

builder.Services.AddAuthentication(o =>
{
    o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
    o.RequireHttpsMetadata = true;
    o.SaveToken = true;
    o.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtAuthenticationManager.JwtSecurityKey)),
        ValidateIssuer = false,
        ValidateAudience = false
    };
});

标准解决方案:自动为HttpClient添加JWT请求头

Blazor WebAssembly中需要配置HttpClient的消息处理程序,自动从自定义AuthenticationStateProvider获取JWT令牌并添加到请求头中,具体步骤如下:

1. 实现自定义AuthorizationMessageHandler

创建一个消息处理程序,拦截HttpClient请求并注入认证头:

public class JwtAuthorizationMessageHandler : DelegatingHandler
{
    private readonly IServiceProvider _serviceProvider;

    public JwtAuthorizationMessageHandler(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        using var scope = _serviceProvider.CreateScope();
        var authStateProvider = scope.ServiceProvider.GetRequiredService<JwtAuthenticationStateProvider>();
        var token = await authStateProvider.GetToken();

        if (!string.IsNullOrEmpty(token))
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

2. 在Blazor WebAssembly的Program.cs中注册HttpClient

注册带有自定义消息处理程序的HttpClient实例,供组件注入使用:

builder.Services.AddScoped<JwtAuthorizationMessageHandler>();

builder.Services.AddHttpClient("ServerAPI", client => 
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<JwtAuthorizationMessageHandler>();

// 注册供组件直接注入的HttpClient
builder.Services.AddScoped(sp => 
    sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));

3. 确保服务器端启用认证中间件

在服务器端的Program.cs中,必须在路由之后添加认证和授权中间件:

app.UseRouting();

// 顺序不能错:先认证,再授权
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

关键说明

  • API控制器是HTTP请求驱动的,不能使用Blazor的CascadingParameter获取AuthenticationState,必须通过请求头中的JWT令牌完成用户认证。
  • 自定义消息处理程序会自动为指定地址的请求添加Authorization: Bearer {token}头,服务器端的JWT认证中间件会验证令牌并自动填充User对象,此时User?.Identity?.Name就能正常获取到用户名。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 14:19:58