Blazor Server Side应用如何存储JWT令牌以访问独立API服务器?
Blazor Server Side 存储JWT令牌的可行方案
核心思路:利用Circuit作用域的服务器端存储
Blazor Server每个用户会话对应一个独立的Circuit,服务器端的Scoped服务会绑定到这个Circuit,是最可靠的会话级存储方式。
1. 创建令牌存储服务
public interface ITokenStorage { string? Token { get; set; } Task<string?> GetTokenAsync(); Task SetTokenAsync(string? token); } public class TokenStorage : ITokenStorage { private string? _token; public string? Token { get => _token; set => _token = value; } public Task<string?> GetTokenAsync() { return Task.FromResult(_token); } public Task SetTokenAsync(string? token) { _token = token; return Task.CompletedTask; } }
2. 注册Scoped服务
在Program.cs中添加:
builder.Services.AddScoped<ITokenStorage, TokenStorage>();
3. 登录时存储令牌
在登录组件中注入ITokenStorage,获取JWT后存入:
@inject ITokenStorage TokenStorage @inject HttpClient HttpClient @inject NavigationManager NavigationManager // 登录逻辑示例 private async Task HandleLogin() { var loginRequest = new { Username = Username, Password = Password }; var response = await HttpClient.PostAsJsonAsync("https://your-api-server/auth/login", loginRequest); if (response.IsSuccessStatusCode) { var tokenResponse = await response.Content.ReadFromJsonAsync<TokenResponse>(); await TokenStorage.SetTokenAsync(tokenResponse.AccessToken); NavigationManager.NavigateTo("/"); } }
4. 调用API时自动携带令牌
封装一个API请求服务,自动从ITokenStorage获取令牌并添加到请求头:
public class ApiService { private readonly HttpClient _httpClient; private readonly ITokenStorage _tokenStorage; public ApiService(HttpClient httpClient, ITokenStorage tokenStorage) { _httpClient = httpClient; _tokenStorage = tokenStorage; } public async Task<T?> GetAsync<T>(string url) { var token = await _tokenStorage.GetTokenAsync(); if (!string.IsNullOrEmpty(token)) { _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); } var response = await _httpClient.GetFromJsonAsync<T>(url); return response; } // 同理封装Post、Put、Delete等方法 }
注册这个服务:
builder.Services.AddScoped<ApiService>();
兼顾后续JS框架迁移的方案:结合LocalStorage
如果要为一年后切换到JS框架做准备,可以将令牌同时存在服务器端Scoped存储和前端LocalStorage:
1. 添加LocalStorage操作服务
public interface ILocalStorageService { Task<string?> GetItemAsync(string key); Task SetItemAsync(string key, string value); Task RemoveItemAsync(string key); } public class LocalStorageService : ILocalStorageService { private readonly IJSRuntime _jsRuntime; public LocalStorageService(IJSRuntime jsRuntime) { _jsRuntime = jsRuntime; } public async Task<string?> GetItemAsync(string key) { return await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", key); } public async Task SetItemAsync(string key, string value) { await _jsRuntime.InvokeVoidAsync("localStorage.setItem", key, value); } public async Task RemoveItemAsync(string key) { await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", key); } }
注册为Scoped服务:
builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();
2. 登录时同步存储
// 登录成功后 await TokenStorage.SetTokenAsync(tokenResponse.AccessToken); await LocalStorageService.SetItemAsync("authToken", tokenResponse.AccessToken);
3. 页面初始化时恢复令牌
在根组件(如MainLayout.razor)的OnInitializedAsync中恢复令牌:
@inject ITokenStorage TokenStorage @inject ILocalStorageService LocalStorageService protected override async Task OnInitializedAsync() { var token = await LocalStorageService.GetItemAsync("authToken"); if (!string.IsNullOrEmpty(token) && string.IsNullOrEmpty(await TokenStorage.GetTokenAsync())) { await TokenStorage.SetTokenAsync(token); } }
这样用户刷新页面后,服务器端Circuit重建时能从LocalStorage恢复令牌,后续切换JS框架时也能直接读取LocalStorage中的令牌。
为什么之前的方案无效?
- Cookie:Blazor Server中直接操作Cookie需在初始渲染/重定向阶段,SignalR连接建立后修改Cookie不会即时生效;若用HttpOnly Cookie,服务器端需通过
IHttpContextAccessor读取,但要注意Circuit初始化时获取,后续HttpContext可能不可用。 - LocalStorage:之前可能只存了前端,没同步到服务器端Scoped存储,导致服务器端调用API时拿不到令牌。
- 会话内存:若未正确注册为Scoped服务,可能导致令牌被多个会话共享,或Circuit重建后丢失。
内容的提问来源于stack exchange,提问作者Age of Empires
相关产品推荐
相关产品推荐

