Blazor登录注册模块实战:伪造后端实现认证流程与迁移指南
简介这份资源是面向 .NET 开发者与 Blazor 初学者的登录注册模块示例基于 Blazor WebAssembly 与 C# 构建通过伪造后端模拟数据与本地存储替代真实 API帮助读者在服务端未就绪时也能完整跑通身份验证与授权流程。压缩包共 61 个文件约 828KB以 16 个 cs 源码、15 个 razor 组件、4 个 json 配置与 4 个 css 样式为主另含 csproj、sln、wwwroot 静态资源及字体文件结构清晰便于按模块阅读。目前已有 500 人学习下载。项目覆盖 Blazor 组件化开发、表单验证、状态管理、路由导航与 AuthenticationStateProvider 认证状态管理等关键知识点并借助 FakeBackendHandler、HttpService、AccountService、LocalStorageService 等服务类演示模拟 API 调用与数据持久化配合 Alert 组件与错误处理机制读者可据此掌握无真实后端场景下的完整登录注册实现思路。1. 拆开一个 Blazor 登录注册模块为什么用伪造后端反而更值得看前端圈子里有个挺反直觉的现象越是带完整后端依赖的示例项目越容易在本地跑不起来。数据库版本对不上、连接字符串写错、种子数据缺失随便一个环节都能卡住半天。所以当我看到这个blazor-login-register项目时第一反应不是“怎么没接真后端”而是“终于有人把认证流程单独拎出来了”。它用伪造的后端服务模拟了登录、注册、令牌校验这几条核心链路整个项目不依赖任何外部数据库或 API克隆下来就能跑。对于正在用 C# 做 Blazor 项目、需要快速搭一套认证骨架的开发者来说这份源码的价值不在于“能直接上线”而在于它把认证模块的交互逻辑、状态管理和页面跳转控制暴露得足够清楚。你可以把它当成一个可运行的教学切片先理解流程再替换成自己的真实后端。2. 伪造后端的设计逻辑AuthenticationStateProvider 与内存用户表2.1 为什么选伪造后端而不是 Mock 框架常见做法是用 Moq 或 NSubstitute 这类 Mock 框架来模拟服务接口但这个项目走了一条更直接的路手写一个FakeAuthenticationService内部维护一个内存用户列表所有认证操作都在这份列表上完成。这样做的好处是零依赖、零配置打开就能调试。Mock 框架虽然灵活但配置语法本身就有学习成本而且一旦接口签名变动Mock 设置全得跟着改。手写伪造服务则把“用户数据从哪来、密码怎么比对、令牌怎么生成”这些逻辑全部摊开在代码里你读一遍就知道认证流程的每个环节在干什么。从选型角度看这个项目适合以下场景你正在搭建 Blazor 项目的前端认证骨架后端 API 还没就绪或者你想理解AuthenticationStateProvider的工作机制但不想被 JWT 签名、数据库查询这些外围逻辑干扰。它不适合直接用于生产环境因为内存用户表重启即丢失密码也是明文比对没有任何哈希处理。2.2 核心服务类的实现拆解项目里最关键的几个类分别是FakeAuthenticationService、CustomAuthenticationStateProvider和几个 Razor 页面组件。先看伪造服务的接口定义public interface IAuthenticationService { TaskAuthResult LoginAsync(string username, string password); TaskAuthResult RegisterAsync(string username, string password); Task LogoutAsync(); TaskUserInfo? GetCurrentUserAsync(); }这个接口把认证操作收敛成四个方法返回值统一用AuthResult包装里面包含是否成功、错误信息和令牌字符串。接下来是伪造实现的核心部分public class FakeAuthenticationService : IAuthenticationService { // 内存用户表Key 为用户名Value 为密码和角色 private readonly Dictionarystring, (string Password, string Role) _users new() { [admin] (123456, Admin), [user1] (password, User) }; // 模拟令牌存储实际项目中应使用 JWT 或 Session private string? _currentToken; public TaskAuthResult LoginAsync(string username, string password) { if (!_users.TryGetValue(username, out var user)) return Task.FromResult(AuthResult.Fail(用户不存在)); if (user.Password ! password) return Task.FromResult(AuthResult.Fail(密码错误)); // 生成一个简单的伪令牌仅用于演示 _currentToken Convert.ToBase64String( Encoding.UTF8.GetBytes(${username}:{DateTime.UtcNow.Ticks})); return Task.FromResult(AuthResult.Success(_currentToken, user.Role)); } public TaskAuthResult RegisterAsync(string username, string password) { if (_users.ContainsKey(username)) return Task.FromResult(AuthResult.Fail(用户名已存在)); _users[username] (password, User); return Task.FromResult(AuthResult.Success(null, User)); } public Task LogoutAsync() { _currentToken null; return Task.CompletedTask; } public TaskUserInfo? GetCurrentUserAsync() { if (string.IsNullOrEmpty(_currentToken)) return Task.FromResultUserInfo?(null); // 从伪令牌中解析用户名 var raw Encoding.UTF8.GetString(Convert.FromBase64String(_currentToken)); var username raw.Split(:)[0]; return Task.FromResultUserInfo?(new UserInfo(username, _users[username].Role)); } }这段代码有几个参数值得注意。_users字典预置了两个账号方便你启动后直接测试登录。令牌生成用的是 Base64 编码加上时间戳没有任何签名验证所以只能用于本地演示。GetCurrentUserAsync从令牌反解用户名再查字典拿角色这个设计模拟了真实场景中“根据令牌获取用户信息”的流程只是省去了 JWT 解析的步骤。2.3 自定义 AuthenticationStateProvider 的接入方式Blazor 的认证状态管理依赖AuthenticationStateProvider这个抽象类。项目里继承它并重写了GetAuthenticationStateAsync方法public class CustomAuthenticationStateProvider : AuthenticationStateProvider { private readonly IAuthenticationService _authService; public CustomAuthenticationStateProvider(IAuthenticationService authService) { _authService authService; } public override async TaskAuthenticationState GetAuthenticationStateAsync() { var user await _authService.GetCurrentUserAsync(); if (user null) { // 未登录状态返回空 ClaimsPrincipal return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); } // 构建已登录用户的 ClaimsIdentity var claims new[] { new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Role, user.Role) }; var identity new ClaimsIdentity(claims, FakeAuth); return new AuthenticationState(new ClaimsPrincipal(identity)); } // 登录/登出后手动通知 Blazor 刷新认证状态 public void NotifyAuthenticationStateChanged() { NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } }这里的关键点是ClaimsIdentity的构造。认证类型参数传了FakeAuth这个字符串在真实项目中通常对应 JWT Bearer 或 Cookie 认证方案。NotifyAuthenticationStateChanged方法暴露给页面组件调用登录成功后手动触发Blazor 才会重新渲染受保护的内容。如果不调用这个方法即使用户已经登录页面上的AuthorizeView也不会更新这是新手最容易踩的坑之一。3. 从零跑起来环境配置、页面路由与登录注册流程3.1 环境准备与项目启动这个项目基于 .NET 6 或 .NET 7 的 Blazor WebAssembly 模板具体版本以你下载后的.csproj文件为准。启动前确认本机已安装对应版本的 .NET SDK然后在项目根目录执行# 检查 SDK 版本确保与项目目标框架匹配 dotnet --list-sdks # 还原依赖包 dotnet restore # 启动项目默认监听 localhost 的某个端口 dotnet run启动后浏览器打开控制台输出的地址通常会直接跳到登录页。如果你看到的是空白页或 404先检查Program.cs里的路由配置是否完整。项目用的是 Blazor WebAssembly 模式所有路由都在客户端处理所以不需要额外的服务器路由配置。3.2 服务注册与依赖注入配置在Program.cs里伪造服务和自定义状态提供器需要注册到 DI 容器var builder WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.AddApp(#app); // 注册伪造认证服务为单例保证用户表在会话期间共享 builder.Services.AddSingletonIAuthenticationService, FakeAuthenticationService(); // 注册自定义认证状态提供器 builder.Services.AddScopedCustomAuthenticationStateProvider(); builder.Services.AddScopedAuthenticationStateProvider(sp sp.GetRequiredServiceCustomAuthenticationStateProvider()); // 启用 Blazor 的授权组件 builder.Services.AddAuthorizationCore(); await builder.Build().RunAsync();注意FakeAuthenticationService注册为Singleton这样内存用户表在整个应用生命周期内保持一致。如果注册成Scoped或Transient每次请求都会创建新实例注册的用户下次刷新就没了。CustomAuthenticationStateProvider注册为Scoped是 Blazor 的惯例因为它需要跟随组件生命周期。AddAuthorizationCore()这行不能漏否则AuthorizeView和[Authorize]特性都不会生效。3.3 登录页与注册页的交互实现登录页面的核心逻辑集中在code块里处理表单提交和状态跳转page /login inject IAuthenticationService AuthService inject CustomAuthenticationStateProvider AuthStateProvider inject NavigationManager Nav EditForm ModelloginModel OnValidSubmitHandleLogin DataAnnotationsValidator / InputText bind-ValueloginModel.Username placeholder用户名 / InputText bind-ValueloginModel.Password typepassword placeholder密码 / button typesubmit登录/button /EditForm if (!string.IsNullOrEmpty(errorMessage)) { p classerrorerrorMessage/p } code { private LoginModel loginModel new(); private string? errorMessage; private async Task HandleLogin() { var result await AuthService.LoginAsync(loginModel.Username, loginModel.Password); if (result.Succeeded) { // 通知认证状态变更触发全局刷新 AuthStateProvider.NotifyAuthenticationStateChanged(); Nav.NavigateTo(/); } else { errorMessage result.ErrorMessage; } } }这段代码里OnValidSubmit配合DataAnnotationsValidator做前端校验HandleLogin调用伪造服务完成认证。登录成功后先调NotifyAuthenticationStateChanged再跳转首页。顺序不能反否则首页可能仍然显示未登录状态。注册页的逻辑类似区别在于注册成功后通常不自动登录而是提示用户跳转到登录页。你可以根据实际需求调整这个行为比如注册后直接调LoginAsync实现自动登录。3.4 路由守卫与页面权限控制Blazor 里控制页面访问权限有两种方式。一种是在组件顶部加attribute [Authorize]另一种是用AuthorizeView包裹内容。项目里对需要登录的页面用了前者page /dashboard attribute [Authorize] inject IAuthenticationService AuthService h3欢迎currentUser?.Username/h3 code { private UserInfo? currentUser; protected override async Task OnInitializedAsync() { currentUser await AuthService.GetCurrentUserAsync(); } }加了[Authorize]之后未登录用户访问/dashboard会被重定向到登录页。重定向的目标路径在App.razor的CascadingAuthenticationState和AuthorizeRouteView里配置。如果你发现未登录时没有跳转而是直接渲染了空白页检查App.razor里是否漏了NotAuthorized模板。4. 避坑与排查伪造后端场景下最容易翻车的五个点4.1 刷新页面后登录状态丢失现象登录成功后跳转首页正常但按 F5 刷新浏览器页面变回未登录状态。原因伪造服务的用户表和令牌都存在内存里Blazor WebAssembly 刷新相当于重新启动应用所有单例服务重新初始化内存数据全部清空。解决这是伪造后端的固有局限不是 Bug。如果需要在刷新后保持登录得把令牌存到localStorage或sessionStorage启动时从存储中恢复。常见做法是在Program.cs里注入ProtectedLocalStorage在GetAuthenticationStateAsync里先读本地存储再决定返回什么状态。4.2 登录后 AuthorizeView 不更新现象调用LoginAsync返回成功但页面上的AuthorizeView仍然显示未登录内容。原因AuthenticationStateProvider不会自动感知认证状态变化必须手动调用NotifyAuthenticationStateChanged。解决在登录和登出逻辑里显式调用AuthStateProvider.NotifyAuthenticationStateChanged()。注意这个方法名和基类方法重名项目里做了重载处理调用的是无参版本。如果你自己实现时遇到编译错误检查方法签名是否和基类冲突。4.3 注册重复用户名时页面无响应现象用已存在的用户名注册点击提交后页面没有任何提示也没有报错。原因RegisterAsync返回了AuthResult.Fail但注册页面的HandleRegister方法没有处理失败分支或者错误信息没有绑定到 UI 上。解决检查注册页的code块确保result.Succeeded为 false 时把result.ErrorMessage赋值给页面变量并渲染出来。这个项目的登录页做了错误展示注册页可能简化了需要你补上。4.4 依赖注入生命周期配错导致用户表不共享现象注册一个用户后登录时提示“用户不存在”。原因FakeAuthenticationService注册成了Transient或Scoped每次注入都创建新实例注册和登录用的是两个不同的用户表。解决确认Program.cs里注册的是AddSingletonIAuthenticationService, FakeAuthenticationService()。Singleton 保证整个应用生命周期内只有一个实例用户表才能跨页面共享。4.5 路由跳转后认证状态未同步现象登录成功后Nav.NavigateTo(/)跳转首页首页显示的还是未登录状态手动刷新后才正常。原因NavigateTo触发了组件重新渲染但认证状态的更新是异步的渲染时GetAuthenticationStateAsync可能还没返回新状态。解决确保NotifyAuthenticationStateChanged在NavigateTo之前调用并且用await等待状态更新完成。如果问题依旧可以在目标页面的OnInitializedAsync里主动调一次GetAuthenticationStateAsync强制刷新。5. 替换成真实后端从伪造服务到 HTTP 客户端的迁移路径伪造后端跑通之后下一步自然是接真实 API。迁移的核心思路是把FakeAuthenticationService换成HttpAuthenticationService接口不变实现从内存操作改成 HTTP 请求。下面是一个迁移后的登录方法示例public class HttpAuthenticationService : IAuthenticationService { private readonly HttpClient _http; private readonly ProtectedLocalStorage _storage; public HttpAuthenticationService(HttpClient http, ProtectedLocalStorage storage) { _http http; _storage storage; } public async TaskAuthResult LoginAsync(string username, string password) { var response await _http.PostAsJsonAsync(api/auth/login, new { username, password }); if (!response.IsSuccessStatusCode) return AuthResult.Fail(登录失败请检查用户名和密码); var result await response.Content.ReadFromJsonAsyncLoginResponse(); // 将真实 JWT 存入本地存储供后续请求携带 await _storage.SetAsync(auth_token, result.Token); return AuthResult.Success(result.Token, result.Role); } public async TaskUserInfo? GetCurrentUserAsync() { var tokenResult await _storage.GetAsyncstring(auth_token); if (!tokenResult.Success) return null; // 解析 JWT 载荷获取用户信息或调用 /api/auth/me 接口 var handler new JwtSecurityTokenHandler(); var jwt handler.ReadJwtToken(tokenResult.Value); var username jwt.Claims.First(c c.Type name).Value; var role jwt.Claims.First(c c.Type role).Value; return new UserInfo(username, role); } }迁移时需要注意几个参数变化。HttpClient的BaseAddress要指向真实后端地址通常在Program.cs里通过builder.Services.AddScoped(sp new HttpClient { BaseAddress new Uri(builder.HostEnvironment.BaseAddress) })配置。ProtectedLocalStorage是 Blazor 提供的加密本地存储比直接操作localStorage安全一些但注意它只能在 WebAssembly 模式下使用。JWT 解析用System.IdentityModel.Tokens.Jwt包需要额外安装 NuGet 依赖。替换完成后CustomAuthenticationStateProvider不需要改动因为它只依赖IAuthenticationService接口。这就是面向接口编程的好处伪造实现和真实实现可以无缝切换。我一般会保留伪造服务作为开发环境的降级方案通过配置文件决定注册哪个实现。比如在appsettings.Development.json里加一个开关本地开发时用伪造服务联调时切到 HTTP 服务。这样即使后端还没部署前端也不会被阻塞。从那以后我每次搭 Blazor 认证模块都会先把伪造服务跑通确认登录、注册、路由守卫、状态刷新这几条链路没问题再动手接真实 API。这样出问题时排查范围能缩小一半不用在前后端之间来回猜。希望帮到你。本文还有配套的精品资源点击获取