资讯详情

Blazor 伪造后端登录注册:接口契约、Token 校验与真实 API 平滑迁移

📅 2026/10/19 0:37:51 | 华诺云谱 👁 阅读
Blazor 伪造后端登录注册:接口契约、Token 校验与真实 API 平滑迁移
简介基于Blazor WebAssembly实现的登录与注册模块面向C#/.NET开发者解决无需JavaScript库即可完成前端交互与身份验证的问题采用伪造后端模拟API调用以降低开发门槛。压缩包共61个文件以.cs源码、.razor组件、.json配置和.css样式为主整体仅828KB目录按Services、Models、Pages、Shared等模块划分便于逐段阅读。目前已有500人学习下载。示例涵盖Blazor组件化开发、数据注解式表单验证、路由导航与NavigationManager跳转并通过FakeBackendHandler拦截HttpClient请求配合localStorage保存用户会话同时演示AuthenticationStateProvider管理认证状态。对于希望快速上手Blazor WebAssembly组件复用、客户端状态管理和前后端交互模拟的初学者是一份结构清晰、可直接运行的参考实现。1. 为什么要在 Blazor 里先搭一套伪造后端登录注册后端接口还在设计阶段前端页面却已经画好了登录和注册弹窗这种情况在 C# 项目里太常见了。等接口联调会拖慢进度硬编一个固定返回又无法覆盖“用户已存在”“密码错误”“Token 过期”这些真实分支。blazor-login-register 的思路是把伪造后端作为项目的一等公民用内存或 JSON 文件存储用户在 Blazor 应用内实现一套完整的登录、注册、Token 校验流程。这套方案的直接价值是让前端认证模块先闭环业务逻辑、表单校验、状态管理都能独立验证。适合正在做 Blazor WebAssembly 或 Blazor Server 认证模块的 C# 开发者尤其是后端进度还不明确、又不想干等接口的团队。2. 伪造后端从哪下手先定接口契约再选数据载体2.1 接口契约登录、注册、校验三个端点必须先写清楚伪造后端不能只在组件里写两个 if 就完事那样登录注册逻辑绑死在 UI 层后面替换真实 API 几乎要重写。我一般会先写一份接口约定用 C# 的 record 类型把请求和响应模型固定下来让前端和后端都能对着同一份数据结构开发。// Models/AuthModels.cs public record LoginRequest(string Username, string Password); public record RegisterRequest(string Username, string Password, string Email); public record AuthResponse(bool Success, string Token, string Message, string Username); // 伪造后端校验 Token 用的请求/响应 public record TokenValidationRequest(string Token); public record TokenValidationResponse(bool IsValid, string Username);这里把 LoginRequest 和 RegisterRequest 定义成纯数据载体是因为登录注册字段在前后端之间传播时不应该夹杂任何 UI 特性。用 record 而不是 class除了语法更简洁还能免费获得值比较能力后面写单元测试断言时可以直接比较两个对象字段。参数说明Username 和 Password 是必须字段Email 在注册请求里可空但建议保留。AuthResponse 里的 Message 字段承载错误码比如user_not_found、bad_password、username_taken前端拿 Error Code 做映射而不是直接展示英文字符串。TokenValidationRequest 设计成独立模型是为了让前端的“每次请求都带 Token”习惯从第一天就养成避免后续换成真实 API 时手忙脚乱。定义好模型后伪造后端必须把所有路由集中到一个服务类里不能散落在各个 razor 页面中。下面给出核心方法的签名// Services/MockAuthService.cs public class MockAuthService { public TaskAuthResponse LoginAsync(LoginRequest request); public TaskAuthResponse RegisterAsync(RegisterRequest request); public TaskTokenValidationResponse ValidateTokenAsync(string token); public Taskbool LogoutAsync(string token); }每个方法返回 Task 而不是同步结果这是刻意模仿真实 HTTP 调用的异步模式。Blazor 组件里统一用await调用将来替换成 HttpClient 时不用改调用点。如果伪造后端返回同步结果组件代码里就不需要 async/await等接真实 API 时要改一堆地方这就是给自己挖坑。2.2 数据载体用内存字典还是 JSON 文件区别在重启后的状态伪造后端存储用户有两条路线存在静态 Dictionary 里实现最快但 Blazor 应用重新编译或刷新进程后数据全部丢失写进本地 JSON 文件数据持久化在磁盘适合反复调试注册功能时不用每次重新创建账号。这个项目建议选 JSON 文件因为登录注册的测试场景天然跨页面跳转需要重启服务调试 UI 样式如果每次重启都要从头注册效率太低。// Services/UserStore.cs public class UserStore { private readonly string _storagePath; private Dictionarystring, UserRecord _users; public UserStore(IWebHostEnvironment env) { _storagePath Path.Combine(env.ContentRootPath, App_Data, users.json); _users LoadUsers(); } private Dictionarystring, UserRecord LoadUsers() { if (!File.Exists(_storagePath)) { return new Dictionarystring, UserRecord( StringComparer.OrdinalIgnoreCase); } var json File.ReadAllText(_storagePath); return JsonSerializer.DeserializeDictionarystring, UserRecord(json) ?? new Dictionarystring, UserRecord( StringComparer.OrdinalIgnoreCase); } public void SaveUsers() { var dir Path.GetDirectoryName(_storagePath); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); var json JsonSerializer.Serialize(_users, new JsonSerializerOptions { WriteIndented true }); File.WriteAllText(_storagePath, json); } }这个 UserRecord 类要单独定义字段应该明确区分存储字段和业务字段public class UserRecord { public string Username { get; set; } public string PasswordHash { get; set; } public string Email { get; set; } public DateTime CreatedAt { get; set; } }注意 LoadUsers 里给 Dictionary 用了StringComparer.OrdinalIgnoreCase这样“Tom”和“tom”会被认为同一个用户符合大多数真实系统的业务规则。如果漏掉这个比较器伪造后端的行为和真实数据库的 unique 索引不一致等接真实 API 时才发现大小写用户名重复注册血泪代价很大。SaveUsers 方法在每次注册成功或修改密码后调用不要放在构造里。JSON 文件路径中的 App_Data 目录可以手动创建也可以用Directory.CreateDirectory兜底。文件格式用缩进模式方便开发时直接打开查看注册了哪些用户。2.3 Token 的伪造逻辑用 GUID 加时间戳模拟 JWT别真的去搞密钥真实 JWT 要用密钥签名、配置发行人、受众这套东西放在伪造后端里会干扰精力。伪造 Token 的目的只是让前端有东西可存、可传、可校验所以用一个带过期时间的 Base64 字符串就够了。public string GenerateToken(string username) { var quote ${Guid.NewGuid():N}|{DateTime.UtcNow.AddHours(2):O}|{username}; return Convert.ToBase64String(Encoding.UTF8.GetBytes(quote)); } public bool TryValidateToken(string token, out string username) { username null; if (string.IsNullOrEmpty(token)) return false; try { var decoded Encoding.UTF8.GetString(Convert.FromBase64String(token)); var parts decoded.Split(|); if (parts.Length ! 3) return false; var expireTime DateTime.Parse(parts[1], null, DateTimeStyles.RoundtripKind); if (expireTime DateTime.UtcNow) return false; username parts[2]; return true; } catch { return false; } }GenerateToken 里把 GUID、过期时间、用户名用竖线分隔拼接后 Base64 编码。GUID 保证每次生成的 Token 都不同防止前端把固定 Token 硬编码。过期时间用 ISO 8601 格式是为了统一解析避免不同环境下的日期格式歧义。TryValidateToken 里拆解字符串后先检查是否过期再取出用户名。这里刻意没有验证签名或篡改因为伪造后端不需要防御攻击只需要模拟“Token 无效”的返回路径。前端如果手动改乱了 Token解析时会走 catch 分支返回 false这在验证前端跳转逻辑时完全够用。参数说明过期时间默认 2 小时你可以改成AddMinutes(30)来测试前端自动登出的计时逻辑。如果你需要模拟 refresh token 刷新机制可以再生成一个 refreshToken 字段但基本版里一个 Token 字段就够加多了反而让前端误以为要去实现刷新流程。2.4 把 Mock 服务挂到应用里延迟与异常分支不能少伪造后端还需要模拟网络延迟和异常抛出否则前端永远不知道真实接口慢起来是什么体验。注册和登录方法里我一般让响应延迟 300ms 到 800ms随机取值这样 Loading 状态就能真实展示。public async TaskAuthResponse LoginAsync(LoginRequest request) { await Task.Delay(Random.Shared.Next(300, 800)); if (!_users.TryGetValue(request.Username, out var user)) { return new AuthResponse(false, null, user_not_found, null); } if (user.PasswordHash ! HashPassword(request.Password)) { return new AuthResponse(false, null, bad_password, null); } var token GenerateToken(request.Username); return new AuthResponse(true, token, ok, request.Username); }HashPassword 这里哪怕用最原始的 SHA256 也行关键是让前端感觉到“密码不是纯文本存库”。模拟延迟的另一个作用是暴露前端的重复点击问题——如果组件没有 Lock 按钮用户会在 800ms 里提交好几遍导致重复注册。这个延迟就是故意用来逼你写 Loading 状态处理的。路由注册方面如果你把 Mock 服务以一个独立的 ASP.NET Core Minimal API 跑在另一个端口那么注意在 Program.cs 里加 CORS因为 Blazor WebAssembly 从浏览器调跨域 API 会被拦。但如果选择直接作为服务注入到 Blazor 应用内部就不存在 CORS 问题这也是伪造后端最常见的做法。3. Blazor 登录注册组件的实现表单校验与状态管理3.1 注册组件两次密码校验的两种做法注册页面最常见的业务规则是“确认密码必须一致”这个校验可以放在前端表单层也可以放在伪造后端做二次校验。实际项目中前端用 DataAnnotations 解决第一层后端再检查一遍是为了兼容没有前端的客户端调用。伪造后端阶段只需做前端校验。* Pages/Register.razor * inject MockAuthService AuthService EditForm Model_model OnValidSubmitHandleSubmit DataAnnotationsValidator / ValidationSummary / div classform-group label用户名/label InputText bind-Value_model.Username classform-control / ValidationMessage For() _model.Username / /div div classform-group label密码/label InputText bind-Value_model.Password typepassword classform-control / ValidationMessage For() _model.Password / /div div classform-group label确认密码/label InputText bind-Value_model.ConfirmPassword typepassword classform-control / ValidationMessage For() _model.ConfirmPassword / /div button typesubmit classbtn btn-primary注册/button /EditForm注意这里用的模型是一个单独的 RegisterModel而不是直接使用 RegisterRequest。因为 ConfirmPassword 字段只属于前端 UI不该进入后端契约。RegisterModel 里的验证特性是第一种做法public class RegisterModel { [Required(ErrorMessage 用户名不能为空)] [MinLength(3, ErrorMessage 用户名至少3位)] public string Username { get; set; } [Required(ErrorMessage 密码不能为空)] [MinLength(6, ErrorMessage 密码至少6位)] public string Password { get; set; } [Required(ErrorMessage 确认密码不能为空)] [Compare(nameof(Password), ErrorMessage 两次密码不一致)] public string ConfirmPassword { get; set; } }Compare是 DataAnnotations 自带特性比较两个属性值差异时直接显示错误。第二种做法是在 HandleSubmit 里手动判断_model.Password ! _model.ConfirmPassword然后追加到 validation messages。手动做法的好处是可以同时清空两个密码框但代码量更多。我建议用 Compare 特性因为 Blazor 的ValidationMessage会自动显示错误少写不少逻辑。HandleSubmit 里调用伪造后端后还要处理业务错误private async Task HandleSubmit() { var request new RegisterRequest( _model.Username, _model.Password, _model.Email); var result await AuthService.RegisterAsync(request); if (result.Success) { Navigation.NavigateTo(/login); } else { _errorMessage result.Message switch { username_taken 用户名已被占用, _ 注册失败 }; } }这段映射里username_taken是伪造后端返回的业务错误码前端负责翻译成用户能看懂的文字。这里有一个容易忽略的点Navigation.NavigateTo(/login)最好传一个绝对路径不要用相对路径否则在 Blazor 子路由下会拼出错误路径。3.2 登录组件Loading 状态与错误码映射登录页面比注册页面更强调 Loading 状态因为登录是高频操作用户会在等待时反复点“登录”按钮导致伪造后端收到多个并发请求。就算伪造后端没并发问题真实 API 肯定会遇到。* Pages/Login.razor * EditForm Model_login OnValidSubmitLoginAsync div classform-group label用户名/label InputText bind-Value_login.Username classform-control / /div div classform-group label密码/label InputText bind-Value_login.Password typepassword classform-control / /div if (!string.IsNullOrEmpty(_errorMessage)) { div classalert alert-danger_errorMessage/div } button typesubmit classbtn btn-primary disabled_isSubmitting (_isSubmitting ? 登录中... : 登录) /button /EditForm后台逻辑用_isSubmitting字段控制按钮禁用并且在 finally 里恢复。如果直接尝试用await Task.Delay模拟真实延迟你会发现按钮在点击后没有立即变成 Loading这是因为 EditForm 的 OnValidSubmit 在异步方法里的运行顺序问题解决方案是先将_isSubmitting设为 true然后立刻调用StateHasChanged()。private async Task LoginAsync() { _isSubmitting true; StateHasChanged(); try { var resp await AuthService.LoginAsync( new LoginRequest(_login.Username, _login.Password)); if (resp.Success) { await _authStateProvider.SetTokenAsync(resp.Token, resp.Username); Navigation.NavigateTo(/, forceLoad: false); } else { _errorMessage resp.Message switch { user_not_found 用户名不存在, bad_password 用户名或密码错误, _ 登录暂时不可用 }; } } finally { _isSubmitting false; StateHasChanged(); } }switch 表达式的作用是把后端 Error Code 映射成中文文案。这里的字符串映射是为了演示如果你面临多语言需求可以把它换成 Dictionary 或资源文件。映射逻辑要放在组件里而不是伪造后端因为后端不应该关心前端用户的语言是中文还是英文。3.3 认证状态管理Scoped 服务加事件通知避免组件各存各的登录成功后导航栏要变成用户信息加退出按钮受保护页面要检查 Token 是否存在。如果每个组件各自维护一个 bool 值状态很难同步。常见的架构是定义一个 AuthStateProvider 服务作为认证状态的唯一数据源。// Services/AuthStateProvider.cs public class AuthStateProvider { private readonly MockAuthService _authService; public string Token { get; private set; } public string Username { get; private set; } public bool IsAuthenticated !string.IsNullOrEmpty(Token); public event Action StateChanged; public AuthStateProvider(MockAuthService authService) { _authService authService; } public async Task SetTokenAsync(string token, string username) { Token token; Username username; StateChanged?.Invoke(); await Task.CompletedTask; } public void Logout() { Token null; Username null; StateChanged?.Invoke(); } }这个服务里StateChanged事件告诉所有订阅者“认证状态变了”各组件在OnInitialized里订阅并在事件发生时调用StateHasChanged()刷新 UI。在 Blazor Server 端这个服务要注册成 Scoped 而不是 Singleton否则所有用户共享同一个登录状态那就非常危险。为了简化订阅逻辑可以写一个基类组件public class AuthComponentBase : ComponentBase, IDisposable { [Inject] protected AuthStateProvider AuthState { get; set; } protected override void OnInitialized() { AuthState.StateChanged OnAuthStateChanged; } private void OnAuthStateChanged() InvokeAsync(StateHasChanged); public void Dispose() { AuthState.StateChanged - OnAuthStateChanged; } }其他页面比如 Profile.razor 继承 AuthComponentBase然后在 UI 里判断AuthState.IsAuthenticated。这样做的好处是登录和退出操作触发事件后所有页面自动刷新不需要手工写导航逻辑。3.4 受保护页面的验证逻辑伪造后端阶段就模拟真实管道受保护页面不能只在 UI 上隐藏按钮还要在进入页面时验证 Token 是否仍然有效。伪造后端阶段必须把这个逻辑做成一个可复用方法否则将来替换真实 API 时受保护页面的检查逻辑会被打回重做。// Services/ProtectedPageHelper.cs public static class ProtectedPageHelper { public static async TaskNavigationResult EnsureAuthenticatedAsync( AuthStateProvider authState, MockAuthService authService) { if (!authState.IsAuthenticated) { return NavigationResult.RedirectToLogin; } var validation await authService.ValidateTokenAsync(authState.Token); if (!validation.IsValid) { authState.Logout(); return NavigationResult.RedirectToLogin; } return NavigationResult.Ok; } }在需要保护的组件里先调用这个方法protected override async Task OnInitializedAsync() { var result await ProtectedPageHelper.EnsureAuthenticatedAsync( AuthState, AuthService); if (result NavigationResult.RedirectToLogin) { Navigation.NavigateTo(/login); return; } // 继续加载页面数据 }注意这里调用了ValidateTokenAsync意味着每次进入受保护页面都会调用一次伪造后端的方法。将来换成真实 API 时这就是一次 HTTP 请求。如果你的 Blazor Server 项目对刷新频繁可以改成从 localStorage 读取 Token 并做一次简单的有效期判断但伪造后端阶段建议保持完整验证这样迁移时只要替换ValidateTokenAsync的实现即可。4. 避坑实践伪造后端从入门到翻车的六个常见问题4.1 刷新页面后登录状态丢失现象登录成功后跳转到首页一切正常但按 F5 刷新页面又跳回登录页。原因Blazor WebAssembly 应用进程在刷新时被完全销毁内存里保存的 Token 和用户名全部清空。如果 AuthStateProvider 只是把 Token 存到属性里没有持久化刷新后自然找不到登录态。解决在 AuthStateProvider 初始化时从浏览器 localStorage 读取 Token。可以把 IJSInProcessRuntime 注入服务或者用封装好的 Storage 帮助类。读取后调用ValidateTokenAsync验证是否过期如果有效就恢复 Username。关键点是注册服务时使用 Scoped 生命周期确保每个页面会话拿到的是同一个实例但从 storage 恢复的逻辑只执行一次。4.2 注册成功后跳转登录页仍提示用户已存在现象注册成功后再去登录输入同样的用户名和密码却提示用户名不存在或者反过来提示“用户不存在”。原因伪造后端的 UserStore 在启动时加载了 JSON 文件内容注册时往内存字典里添加了新用户但保存 JSON 文件的时机不对。如果注册回调里没有立即调用SaveUsers()下次启动服务后用户数据丢失自然就“不存在”了。解决注册方法里在成功添加用户后立刻调用SaveUsers()。还有另一个隐藏坑如果注册方法里先检查用户名是否存在再插入用户但是对字典的读和写之间没有加锁并发双击会导致两个请求都通过检查然后抛异常。伪造后端阶段可以用lock (_lockObj)包住检查加写入的操作模拟真实数据库的原子性。4.3 HttpClient 跨域请求被浏览器拦截现象Blazor WebAssembly 项目里的登录按钮点击后控制台报 CORS error请求没有真正发出去。原因伪造后端如果作为独立 ASP.NET Core WebAPI 项目运行而 Blazor WebAssembly 项目跑在另一个 localhost 端口浏览器会拦截跨域 HTTP 响应。解决在伪造后端项目的 Program.cs 里显式添加 CORS 策略。不能用AllowAnyOrigin因为真实项目中不可能放开所有来源这里只允许本地开发地址。可以这样builder.Services.AddCors(options { options.AddPolicy(DevCors, policy policy.SetIsOriginAllowed(origin origin.StartsWith(http://localhost:) || origin.StartsWith(https://localhost:))); }); var app builder.Build(); app.UseCors(DevCors);如果你选择把伪造后端直接注入到 Blazor Server 服务端而不是走 HttpClient那就没有 CORS 问题但那样失去了模拟真实网络请求的意义因为真实 API 一定是跨网络的。4.4 手动改 users.json 文件后程序读不到最新数据现象注册逻辑调试中打开 users.json 看到用户已写入但修改文件内容后重新登录伪造后端用的还是旧数据。原因UserStore 构造函数里把 JSON 文件读到内存字典后后续所有操作都以内存字典为准文件只是启动时的一次性加载。手工改文件后没有触发内存刷新。解决有两种修复思路。一种是每次登录和注册都直接读文件但这样效率低并且代码更繁琐。另一种是在 UserStore 内部维护一个文件监视器或者提供 Refresh 方法只在调试时调。最省事的做法是修改完文件后重启 Blazor 应用伪造后端就能重新加载。如果不想重启那就按下面的代码改成从文件恢复public Dictionarystring, UserRecord GetUsers() { using var fs new FileStream(_storagePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite); using var reader new StreamReader(fs); var json reader.ReadToEnd(); return JsonSerializer.DeserializeDictionarystring, UserRecord(json) ?? new Dictionarystring, UserRecord(); }调用GetUsers()的时机放在每次校验密码之前可以保证手动修改立即生效。这个方法在真实 API 场景下对应的是“每次查询数据库”所以使用它会更贴近最终的交互模式。4.5 密码框用密码管理器自动填充导致校验值不对现象登录页面用浏览器自带密码管理器填充用户名和密码后点登录提示“用户名或密码错误”但手输就能成功。原因Blazor 的InputText默认绑定onchange事件而密码管理器自动填充时不触发 onchange导致模型里的 Password 字段还是空字符串或旧值。解决把密码输入框的绑定事件改成oninput这样每次输入或填充都会更新模型InputText bind-Value_login.Password typepassword bind:eventoninput classform-control /这个小问题在伪造后端阶段就会出现如果不处理迁移到真实 API 时会变成特别玄学的 bug。注意注册页面的确认密码字段同样需要加bind:eventoninput否则自动填充也会导致两次密码不一致的假报错。4.6 替换真实 API 后受保护页面全部 401现象伪造后端阶段一切正常把 MockAuthService 换成 HttpClient 调用真实 API 后任何受保护页面请求都返回 401 Unauthorized。原因伪造后端的 Token 校验方法ValidateTokenAsync只在页面初始化时显式调用并没有在每次 HTTP 请求时自动携带 Authorization Header。真实 API 需要前端在每次请求头里加 Token如果伪造后端阶段没有在 HttpClient 的请求管道里统一添加迁移后自然漏掉。解决在伪造后端阶段就定义一个自定义 DelegatingHandler把 Token 从 AuthStateProvider 取出来加到请求头public class AuthMessageHandler : DelegatingHandler { private readonly AuthStateProvider _authState; public AuthMessageHandler(AuthStateProvider authState) { _authState authState; } protected override async TaskHttpResponseMessage SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (_authState.IsAuthenticated) { request.Headers.Authorization new AuthenticationHeaderValue(Bearer, _authState.Token); } return await base.SendAsync(request, cancellationToken); } }注册 HttpClient 时加上这个 Handlerbuilder.Services.AddScopedAuthStateProvider(); builder.Services.AddTransientAuthMessageHandler(); builder.Services.AddHttpClient(AuthApi) .AddHttpMessageHandlerAuthMessageHandler();这样伪造后端阶段就模拟了“每个请求都自动携带 Token”的管道等替换真实 API 时这个 Handler 可以直接沿用只需要改 BaseAddress 和认证地址。如果你在伪造后端阶段偷懒不做这层迁移时就要在所有调用点手工加 Header那几乎等同于重写。5. 进阶把伪造后端换成真实 API迁移时只需改一个地方先定义 IAuthService 接口把 MockAuthService 和将来的 RealAuthService 都实现它。接口里就三个方法LoginAsync、RegisterAsync、ValidateTokenAsync。组件里全部注入 IAuthService而不是直接注入 MockAuthService。public interface IAuthService { TaskAuthResponse LoginAsync(LoginRequest request); TaskAuthResponse RegisterAsync(RegisterRequest request); TaskTokenValidationResponse ValidateTokenAsync(string token); }伪造后端的 MockAuthService 实现这个接口里面保持现有逻辑。真实 API 的 RealAuthService 用 HttpClient 调用远程端点返回的 AuthResponse 字段完全一致。在 Program.cs 里切换注册方式var useMock builder.Configuration.GetValuebool(UseMockBackend); if (useMock) { builder.Services.AddScopedIAuthService, MockAuthService(); builder.Services.AddScopedUserStore(); } else { builder.Services.AddScopedIAuthService, RealAuthService(); builder.Services.AddScopedHttpClient(sp new HttpClient { BaseAddress new Uri(builder.Configuration[ApiBaseUrl]) }); }UseMockBackend这个配置项放在 appsettings.json 里默认 true部署到测试环境时改成 false。切换后组件代码一行不用改因为接口没变。真正需要改的是 Token 的生成和校验逻辑但那是服务内部实现与 UI 无关。验证迁移是否成功的标准有三个。第一登录成功后在 localStorage 里能看到 Token且 Token 解密后包含用户名和过期时间第二直接访问一个受保护页面 URL未登录状态会跳转登录登录后能正常进入第三手动改坏 Token 后访问受保护页面会被强制登出并回到登录页。这三条都通过说明前端的认证闭环和真实 API 的认证闭环行为一致。另外要关注 Token 刷新机制。伪造后端阶段 Token 过期时间是 2 小时真实 API 可能只有 30 分钟甚至带 refresh token。如果要支持刷新可以在 AuthStateProvider 里加一个 RefreshToken 属性和 TryRefreshAsync 方法。伪造后端阶段可以直接返回一个新的 Token真实 API 阶段则调用刷新端点。前端组件只需要在收到 401 响应时调用 TryRefreshAsync然后重试一次请求。从那以后我每次搭 Blazor 项目不管后端进度如何都会先用伪造后端把认证管道完整实现一遍尤其强制走一遍受保护页面携带 Authorization Header 的逻辑。这个习惯帮我避开了不少替换 API 时的翻车情况希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑