Blazor WASM 如何对接自定义JWT认证API实现身份验证
核心问题
你项目模板默认带的AddOidcAuthentication是为标准OpenID Connect身份提供商设计的,它会自动请求OIDC发现文档、走OAuth2授权码流程,和你当前实现的「用户名密码提交直接返回JWT」的自定义登录接口协议完全不兼容,仅修改Authority字段不可能正常对接。
最简实现方案
不需要强行适配OIDC框架,直接用Blazor提供的自定义认证状态提供者对接现有接口即可,所有原生授权组件(AuthorizeView、[Authorize]特性等)都可以正常使用,步骤如下:
- 清理默认OIDC配置
直接删除Program.cs中builder.Services.AddOidcAuthentication(...)的整段代码,不需要保留相关配置,appsettings.json里的OIDC节点也可以删掉。 - 安装本地存储依赖
引入Blazored.LocalStorage包,用于在浏览器本地持久化存储JWT和刷新令牌,避免页面刷新后登录状态丢失。 - 实现自定义认证状态提供者
新建类继承AuthenticationStateProvider,实现登录、登出、认证状态读取逻辑,参考代码:
public class CustomAuthStateProvider : AuthenticationStateProvider { private readonly HttpClient _httpClient; private readonly ILocalStorageService _localStorage; private readonly ClaimsPrincipal _anonymousUser = new(new ClaimsIdentity()); public CustomAuthStateProvider(HttpClient httpClient, ILocalStorageService localStorage) { _httpClient = httpClient; _localStorage = localStorage; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var savedJwt = await _localStorage.GetItemAsync<string>("jwt_token"); if (string.IsNullOrWhiteSpace(savedJwt)) { return new AuthenticationState(_anonymousUser); } // 可在此处添加JWT过期校验,过期则自动调用刷新令牌接口,刷新失败则清空本地存储返回匿名状态 var claims = ParseJwtClaims(savedJwt); var identity = new ClaimsIdentity(claims, "custom_jwt"); return new AuthenticationState(new ClaimsPrincipal(identity)); } public async Task Login(string username, string password) { var loginResponse = await _httpClient.PostAsJsonAsync("api/account/login", new UserLogin { Username = username, Password = password }); if (!loginResponse.IsSuccessStatusCode) { throw new UnauthorizedAccessException("用户名或密码错误"); } var tokenResult = await loginResponse.Content.ReadFromJsonAsync<LoginResponse>(); await _localStorage.SetItemAsync("jwt_token", tokenResult.JWT); await _localStorage.SetItemAsync("refresh_token", tokenResult.RefreshToken); // 通知框架认证状态变更 NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } public async Task Logout() { await _localStorage.RemoveItemAsync("jwt_token"); await _localStorage.RemoveItemAsync("refresh_token"); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } private IEnumerable<Claim> ParseJwtClaims(string jwt) { var payload = jwt.Split('.')[1]; var jsonBytes = DecodeBase64WithoutPadding(payload); var claimDict = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes); return claimDict.Select(kv => new Claim(kv.Key, kv.Value.ToString())); } private byte[] DecodeBase64WithoutPadding(string base64Str) { base64Str = base64Str.Replace('-', '+').Replace('_', '/'); return base64Str.Length % 4 switch { 2 => Convert.FromBase64String(base64Str + "=="), 3 => Convert.FromBase64String(base64Str + "="), _ => Convert.FromBase64String(base64Str) }; } } public class LoginResponse { public string JWT { get; set; } public string RefreshToken { get; set; } }
- 注册相关服务
在Program.cs中添加服务注册,同时配置HttpClient自动在请求头携带JWT:
builder.Services.AddBlazoredLocalStorage(); builder.Services.AddAuthorizationCore(); builder.Services.AddScoped<CustomAuthStateProvider>(); builder.Services.AddScoped<AuthenticationStateProvider>(sp => sp.GetRequiredService<CustomAuthStateProvider>()); // 配置HttpClient,建议额外实现DelegatingHandler统一处理请求头注入、401响应跳转登录逻辑 builder.Services.AddScoped(sp => { return new HttpClient { BaseAddress = new Uri("你的后端API根地址") }; });
- 替换默认登录UI
把模板自带的OIDC登录跳转按钮替换为自定义登录表单,表单提交时调用CustomAuthStateProvider的Login方法即可,原有的授权判断逻辑不需要修改。
补充说明
如果硬要使用默认OIDC框架对接,你需要在后端额外实现完整的OIDC协议端点,包括发现文档端点、授权端点、令牌端点、用户信息端点等,工作量远大于上述自定义方案,没有实际必要。
- 生产环境建议打开JWT的Issuer、Audience校验,避免令牌被伪造
- 记得实现刷新令牌的自动轮换逻辑,减少用户重复登录的频率
- 推荐单独实现DelegatingHandler统一处理令牌注入、令牌过期刷新、未授权响应跳转逻辑,不要在每个接口请求里重复处理
内容的提问来源于stack exchange,提问作者tixina3684
相关产品推荐
相关产品推荐

