.NET 8 Blazor自动渲染模式下自定义JWT认证的Token存储问题
Blazor自动渲染模式下Token跨服务端/客户端共享方案
问题背景
我基于Blazor搭建了服务端+客户端项目,服务端负责编写API,应用采用自动渲染模式(同时支持服务端渲染、客户端渲染页面)。已实现CustomAuthenticationStateProvider并在两端完成注册,但核心需求是让Token能在服务端和客户端同时被访问。
当前使用的CustomAuthenticationStateProvider代码如下:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider { private readonly IHttpContextAccessor _httpContextAccessor; private ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity()); public CustomAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var token = _httpContextAccessor.HttpContext.Request.Cookies["token"]; if (string.IsNullOrEmpty(token)) return new AuthenticationState(_anonymous); var tokenHandler = new JwtSecurityTokenHandler(); var identity = new ClaimsIdentity(tokenHandler.ReadJwtToken(token).Claims, "jwt"); var user = new ClaimsPrincipal(identity); return await Task.FromResult(new AuthenticationState(user)); } public void UpdateAuthenticationState(string token) { if (string.IsNullOrEmpty(token)) { _httpContextAccessor.HttpContext.Response.Cookies.Delete("token"); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(_anonymous))); return; } var tokenHandler = new JwtSecurityTokenHandler(); var identity = new ClaimsIdentity(tokenHandler.ReadJwtToken(token).Claims, "jwt"); var user = new ClaimsPrincipal(identity); var cookieOptions = new CookieOptions { Expires = DateTime.Now.AddHours(1), HttpOnly = true, SameSite = SameSiteMode.Strict, Secure = true }; _httpContextAccessor.HttpContext.Response.Cookies.Append("token", token, cookieOptions); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user))); } }
遇到的问题
- 依赖
IHttpContextAccessor时,客户端不存在该服务,报错:blazor.web.js:1 Error: One or more errors occurred. (Unable to resolve service for type 'Microsoft.AspNetCore.Http.IHttpContextAccessor' while attempting to activate 'Client.CustomAuthenticationStateProvider'.) - 改用LocalStorage/SessionStorage存储Token,服务端会因JSRuntime交互报错(服务端无法直接操作浏览器存储)。
解决方案:基于Cookie的跨端兼容实现
核心思路是服务端直接操作Cookie,客户端通过JS读取/写入Cookie,同时让CustomAuthenticationStateProvider兼容两端运行环境。
1. 重构CustomAuthenticationStateProvider
通过可选注入IHttpContextAccessor和IJSRuntime,区分服务端/客户端逻辑:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider { private readonly IHttpContextAccessor? _httpContextAccessor; private readonly IJSRuntime? _jsRuntime; private readonly ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity()); private readonly bool _isServerSide; // 构造函数兼容两端依赖注入 public CustomAuthenticationStateProvider( IHttpContextAccessor? httpContextAccessor = null, IJSRuntime? jsRuntime = null) { _httpContextAccessor = httpContextAccessor; _jsRuntime = jsRuntime; _isServerSide = httpContextAccessor != null; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { string? token = null; if (_isServerSide) { // 服务端直接从Request Cookie读取Token token = _httpContextAccessor.HttpContext.Request.Cookies["token"]; } else { // 客户端通过JS读取Cookie token = await _jsRuntime.InvokeAsync<string>("blazorGetCookie", "token"); } if (string.IsNullOrEmpty(token)) return new AuthenticationState(_anonymous); var tokenHandler = new JwtSecurityTokenHandler(); var identity = new ClaimsIdentity(tokenHandler.ReadJwtToken(token).Claims, "jwt"); return new AuthenticationState(new ClaimsPrincipal(identity)); } public async Task UpdateAuthenticationState(string? token) { ClaimsPrincipal user; if (string.IsNullOrEmpty(token)) { user = _anonymous; if (_isServerSide) { _httpContextAccessor.HttpContext.Response.Cookies.Delete("token"); } else { await _jsRuntime.InvokeVoidAsync("blazorSetCookie", "token", "", -1); } } else { var tokenHandler = new JwtSecurityTokenHandler(); var identity = new ClaimsIdentity(tokenHandler.ReadJwtToken(token).Claims, "jwt"); user = new ClaimsPrincipal(identity); var expiresMinutes = 60; if (_isServerSide) { var cookieOptions = new CookieOptions { Expires = DateTime.Now.AddMinutes(expiresMinutes), HttpOnly = true, SameSite = SameSiteMode.Strict, Secure = true }; _httpContextAccessor.HttpContext.Response.Cookies.Append("token", token, cookieOptions); } else { // 客户端通过JS设置Cookie await _jsRuntime.InvokeVoidAsync("blazorSetCookie", "token", token, expiresMinutes); } } NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user))); } }
2. 添加客户端JS辅助方法
在客户端项目的wwwroot/index.html中添加操作Cookie的JS函数:
<script> // 读取指定名称的Cookie function blazorGetCookie(name) { let value = "; " + document.cookie; let parts = value.split("; " + name + "="); if (parts.length === 2) return parts.pop().split(";").shift(); return null; } // 设置Cookie,minutes为过期分钟数(-1表示删除) function blazorSetCookie(name, value, minutes) { let expires = ""; if (minutes > 0) { let date = new Date(); date.setTime(date.getTime() + (minutes * 60 * 1000)); expires = "; expires=" + date.toUTCString(); } document.cookie = `${name}=${value || ""}${expires}; path=/; secure; sameSite=Strict`; } </script>
3. 两端分别注册服务
- 服务端项目(Program.cs):
// 注册IHttpContextAccessor builder.Services.AddHttpContextAccessor(); // 注入IHttpContextAccessor实例到AuthenticationStateProvider builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>(sp => new CustomAuthenticationStateProvider(sp.GetRequiredService<IHttpContextAccessor>()));
- 客户端项目(Program.cs):
// 注入IJSRuntime实例到AuthenticationStateProvider builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>(sp => new CustomAuthenticationStateProvider(jsRuntime: sp.GetRequiredService<IJSRuntime>()));
关键注意事项
- 保持
HttpOnly=true:服务端设置Cookie时启用该属性,客户端通过页面内JS读取不受影响,同时能防止XSS攻击窃取Token。 - 安全配置:生产环境确保
Secure=true(仅HTTPS传输)、SameSite=Strict(防止CSRF攻击)。 - 环境判断:通过
IHttpContextAccessor是否存在判断运行环境,无需额外依赖环境检测包。
内容的提问来源于stack exchange,提问作者Dhawooth Ibnu Shahaman
相关产品推荐
相关产品推荐

