Blazor Webassembly非标准Bearer令牌授权与请求头访问咨询
直接在客户端Blazor(WebAssembly模式)的CustomAuthenticationStateProvider中访问初始HTTP请求头不可行。Blazor WASM运行在浏览器沙箱环境中,没有权限读取首次加载应用时的原始HTTP请求头集合,不需要硬在AuthenticationStateProvider里找访问请求头的方法,换轻量实现路径即可,全程不需要接入完整OIDC或ASP.NET Core原生安全体系,完全匹配不需要做令牌校验的需求。
1. 应用入口层提取请求头令牌
根据Blazor部署模式选择对应方式拿到初始请求里的MyCustomJWTBearer令牌:
- 如果是ASP.NET Core托管的Blazor WASM:在主机页面
_Host.cshtml渲染根组件时,直接从当前请求头读取令牌,作为组件参数传入Blazor应用:<component type="typeof(App)" render-mode="WebAssemblyPrerendered" param-InitialToken="Context.Request.Headers["MyCustomJWTBearer"].FirstOrDefault()" /> - 如果是独立部署的Blazor WASM:在前置代理/网关层(Nginx、APISIX、CDN等)返回
index.html时,动态将请求头里的MyCustomJWTBearer值注入到页面全局JS变量中,示例:<script> window.__INITIAL_TOKEN__ = "代理层读取到的MyCustomJWTBearer令牌值"; </script> - 如果是Blazor Server模式:直接注入
IHttpContextAccessor即可读取请求头,但注意SignalR连接建立后HttpContext不会持续存在,令牌必须在应用初始化时一次性读取存储,不能每次认证状态查询都从HttpContext取。
2. 实现内存令牌存储服务
注册一个单例服务统一管理令牌和解析后的声明,不需要做任何令牌有效性、发行方校验,仅做存储和基础的JWT payload解析:
public class AppTokenStore { private string? _currentToken; private IReadOnlyList<Claim>? _currentClaims; /// <summary> /// 应用初始化时写入令牌 /// </summary> public void SetToken(string token) { _currentToken = token; _currentClaims = ParseJwtClaims(token).ToList().AsReadOnly(); TokenChanged?.Invoke(); } /// <summary> /// 获取当前存储的原始令牌 /// </summary> public string? GetRawToken() => _currentToken; /// <summary> /// 获取令牌解析后的所有声明 /// </summary> public IReadOnlyList<Claim> GetClaims() => _currentClaims ?? Array.Empty<Claim>(); /// <summary> /// 令牌变更事件,用于触发认证状态刷新 /// </summary> public event Action? TokenChanged; // 仅解析JWT Payload,不做签名、有效期校验 private IEnumerable<Claim> ParseJwtClaims(string jwt) { var payload = jwt.Split('.')[1]; var jsonBytes = DecodeBase64Url(payload); var claims = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes) ?? new Dictionary<string, object>(); return claims.Select(kv => new Claim(kv.Key, kv.Value?.ToString() ?? string.Empty)); } private byte[] DecodeBase64Url(string base64Url) { var base64 = base64Url.Replace('-', '+').Replace('_', '/'); base64 += (base64.Length % 4) switch { 2 => "==", 3 => "=", _ => string.Empty }; return Convert.FromBase64String(base64); } }
在Program.cs中注册服务:
builder.Services.AddSingleton<AppTokenStore>();
3. 应用启动时初始化令牌
如果是托管模式传参的方式,在App.razor中接收初始令牌参数,写入存储服务:
@inject AppTokenStore TokenStore <Router AppAssembly="@typeof(App).Assembly"> <!-- 原有路由配置保持不变 --> </Router> @code { [Parameter] public string? InitialToken { get; set; } protected override void OnInitialized() { if (!string.IsNullOrWhiteSpace(InitialToken)) { TokenStore.SetToken(InitialToken); } } }
如果是代理注入JS全局变量的方式,在Program.cs启动应用前通过JS互操作读取全局变量,写入AppTokenStore即可。
4. 改造自定义认证状态提供器
CustomAuthenticationStateProvider不需要直接访问请求头,直接依赖AppTokenStore拿到解析好的声明生成认证状态即可:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider { private readonly AppTokenStore _tokenStore; public CustomAuthenticationStateProvider(AppTokenStore tokenStore) { _tokenStore = tokenStore; // 令牌变更时自动通知UI刷新认证状态 _tokenStore.TokenChanged += () => { NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); }; } public override Task<AuthenticationState> GetAuthenticationStateAsync() { var claims = _tokenStore.GetClaims(); var identity = claims.Any() ? new ClaimsIdentity(claims, "CustomHeaderAuth") : new ClaimsIdentity(); var principal = new ClaimsPrincipal(identity); return Task.FromResult(new AuthenticationState(principal)); } }
在Program.cs中注册认证相关服务,之后就可以正常使用<AuthorizeView>、[Authorize]特性控制页面元素的显示隐藏:
builder.Services.AddAuthorizationCore(); builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
5. 配置HttpClient自动携带令牌
实现一个自定义消息处理器,所有发往外站的请求自动从存储中读取令牌,添加到MyCustomJWTBearer请求头:
public class CustomTokenHeaderHandler : DelegatingHandler { private readonly AppTokenStore _tokenStore; public CustomTokenHeaderHandler(AppTokenStore tokenStore) { _tokenStore = tokenStore; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var token = _tokenStore.GetRawToken(); if (!string.IsNullOrWhiteSpace(token)) { request.Headers.TryAddWithoutValidation("MyCustomJWTBearer", token); } return await base.SendAsync(request, cancellationToken); } }
注册HttpClient时挂载该处理器:
builder.Services.AddScoped<CustomTokenHeaderHandler>(); builder.Services.AddScoped(sp => { var handler = sp.GetRequiredService<CustomTokenHeaderHandler>(); handler.InnerHandler = new HttpClientHandler(); return new HttpClient(handler) { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }; });
注意:如果后续有令牌刷新需求,只需要调用
AppTokenStore.SetToken()传入新令牌,UI认证状态和后续HttpClient请求会自动同步更新,不需要额外写刷新逻辑。
内容的提问来源于stack exchange,提问作者Adam

