ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Blazor全栈开发认证授权实战:从Identity到JWT的完整指南

Blazor全栈开发认证授权实战:从Identity到JWT的完整指南 1. 认证与授权的核心机制与方案选型写Blazor全栈开发绕不开一个话题认证与授权。很多新手朋友把这两个词混在一起说其实它们是两件事。认证Authentication解决的是“你是谁”的问题授权Authorization解决的是“你能干什么”的问题。认证在前授权在后缺一个都不行。我见过不少Blazor项目前期开发速度快等上了生产环境才发现权限漏洞一堆。尤其是Blazor这种前后端一体的框架很多人把它当纯前端框架用把判断登录态、角色权限的逻辑全写在JavaScript里后端接口完全不设防。这是一个非常危险的误区。Blazor的认证授权方案里有一条铁律**前端控制只是体验优化后端认证授权才是安全底线。**这篇文章我会把Blazor全栈开发中认证授权的完整链路拆开讲包括机制选型、基于ASP.NET Core Identity的落地、策略授权、前端安全防线以及高频踩坑排查希望你读完能少走弯路。1.1 认证与授权的概念边界先花一点时间说清楚概念这对后面的实操很关键。认证通俗讲就是“验明正身”。用户提交用户名密码系统核验通过后发一个凭证Token或Cookie之后用户带着这个凭证访问资源系统知道“哦你是张三”。常见的认证方式有Cookie认证、JWT Bearer认证、OAuth2/OpenID Connect外部登录等。授权是在确认了“你是张三”之后决定“张三能不能点这个按钮、打开这个页面、调用这个接口”。授权依赖角色Role或策略Policy。比如“张三有管理员角色可以删数据”“李四只有访客角色只能看数据”。Blazor全栈开发中需要同时考虑两层Blazor Server的信号R连接鉴权以及WebAssembly客户端的UI级权限控制。两种托管模型下认证授权的实现方式有差异这个我在1.2节详细展开。1.2 Blazor两种托管模型下的认证方案差异Blazor有两种渲染模式对应的安全模型完全不同先搞清楚你用的是哪一种。Blazor Server模式UI渲染在服务端浏览器与服务器之间通过SignalR长连接通信页面上的操作实时与服务端交互。这种模式天然适合用Cookie认证加上ASP.NET Core Identity。登录请求由服务端处理登录成功后生成的认证Cookie由浏览器保存之后的每个SignalR请求都会携带Cookie服务端自动解析出用户身份。你可以直接在组件中用[Authorize]特性或AuthenticationStateProvider获取当前用户非常顺手。Blazor WebAssembly模式客户端在浏览器中运行本质上是一个单页应用页面上的数据请求通过HTTP API完成。这种模式更适合JWT Bearer认证用户登录后将Token存在本地存储要谨慎或内存中每个API请求在Authorization请求头携带Bearer Token后端API通过JWT中间件校验Token有效性。注意WebAssembly前端的任何权限判断都是可被绕过的真正的防线在后端API的授权过滤器中。还有最新的Blazor United.NET 8起的统一模型支持同一项目内混合使用Server和WebAssembly渲染。这种情况下我的推荐是认证统一走Cookie方案同时API层兼容JWT。登录页面用静态SSR渲染业务页面按需采用Server或WebAssembly渲染这样可以把认证逻辑收敛到一处。1.3 为什么我推荐Cookie认证而不是JWT不少从传统前后端分离项目转过来的朋友一上手就选JWT理由是“无状态、可扩展”。但Blazor全栈项目有自己的特点我不太建议在Blazor Server中直接使用JWT作为主要认证方式原因有三第一Blazor Server的SignalR连接需要持久化的身份信息JWT虽然能在请求头传递但SignalR的协商请求和WebSocket连接对Token的处理不如Cookie方便需要额外编写自定义逻辑才能在连接建立时传递Token。第二Cookie认证的注销是即时的。服务端删掉认证Cookie配合数据保护API的撤销机制可以快速让已签发的票据失效。JWT在过期前无法主动撤销如果用户退出登录但你发的Token还有效攻击者拿到Token就能持续访问除非你引入Redis黑名单机制成本一下就上来了。第三ASP.NET Core Identity默认就和Cookie认证深度集成。登录、注销、双因素验证、外部登录Provider都开箱即用。用JWT你还得自己实现登录接口、刷新Token、Token存储等一堆重复工作。当然如果是纯WebAssembly单页应用且需要对接移动端AppJWT方案依然合理。我的建议是Blazor Server优先用CookieIdentityWebAssembly优先用JWTIdentity API混合模式两者都要但分开管理。2. 基于ASP.NET Core Identity的完整认证落地这一节开始进入实战。我以Blazor Server ASP.NET Core Identity做一个完整案例从项目初始化到登录注册页面跑通每一步都拆开讲清楚。后续如果你做WebAssembly模式Identity的套路是一样的只是最后接的是JWT而不是Cookie。2.1 项目初始化与数据上下文准备新建一个Blazor Server项目后第一件事是安装Identity相关的NuGet包同时确认项目中已经有Entity Framework Core。默认项目模板自带EF Core但如果你是空项目起步需要手动引入这些包dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools如果你更习惯用PostgreSQL或者SQLite对应换一下Provider包即可。这里用SQL Server做演示。接下来创建ApplicationUser类继承IdentityUser方便以后扩展自定义字段比如手机号、昵称等。注意这里有个小技巧提前自定义用户实体比项目上线后再改省事得多。using Microsoft.AspNetCore.Identity; public class ApplicationUser : IdentityUser { public string? Nickname { get; set; } public string? AvatarUrl { get; set; } }然后是ApplicationDbContext继承IdentityDbContextApplicationUserusing Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; public class ApplicationDbContext : IdentityDbContextApplicationUser { public ApplicationDbContext(DbContextOptionsApplicationDbContext options) : base(options) { } }Program.cs里注册数据库和Identity服务这是核心配置步骤using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; var builder WebApplication.CreateBuilder(args); // 注册数据库上下文注意替换连接字符串 builder.Services.AddDbContextApplicationDbContext(options options.UseSqlServer(builder.Configuration.GetConnectionString(DefaultConnection))); // 注册Identity服务配置密码策略与用户锁定参数 builder.Services.AddIdentityApplicationUser, IdentityRole(options { // 密码复杂度演示环境适度放宽生产建议更强 options.Password.RequiredLength 6; options.Password.RequireNonAlphanumeric false; options.Password.RequireUppercase false; options.Password.RequireLowercase false; options.Password.RequireDigit true; // 用户锁定的配置 options.Lockout.MaxFailedAccessAttempts 5; options.Lockout.DefaultLockoutTimeSpan TimeSpan.FromMinutes(5); options.Lockout.AllowedForNewUsers true; // 用户名的配置 options.User.AllowedUserNameCharacters abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._; options.User.RequireUniqueEmail true; }) .AddEntityFrameworkStoresApplicationDbContext() .AddDefaultTokenProviders(); var app builder.Build();这段代码里的密码策略和锁定配置值得你仔细调。很多Demo项目把RequireNonAlphanumeric、RequireUppercase、RequireLowercase全部设为true看起来很安全实际测试时连自己都经常记不住密码最后又改回来。我建议在真实项目中根据你的用户群体来设定内部系统要求可以适当严格面向公众的系统要平衡易用性和安全性。2.2 迁移、种子数据与角色初始化服务配置好之后先创建数据库迁移。如果是第一次用EF Core记得先在项目里安装Microsoft.EntityFrameworkCore.Tools然后执行dotnet ef migrations add InitialIdentitySchema dotnet ef database update除了建表强烈建议你写一个种子数据类把初始角色比如管理员和默认用户创建出来。这个不起眼的步骤很多新手会忽略结果部署到测试环境才发现没法分配角色只能手动往数据库插数据。写一个DbInitializer放在项目里每次启动时检查数据库里是否存在角色不存在就创建一劳永逸public static class DbInitializer { public static async Task InitializeAsync(IServiceProvider serviceProvider) { using var scope serviceProvider.CreateScope(); var roleManager scope.ServiceProvider.GetRequiredServiceRoleManagerIdentityRole(); var userManager scope.ServiceProvider.GetRequiredServiceUserManagerApplicationUser(); var logger scope.ServiceProvider.GetRequiredServiceILoggerProgram(); // 确保管理员角色存在 string[] roles { Admin, User }; foreach (var role in roles) { if (!await roleManager.RoleExistsAsync(role)) { await roleManager.CreateAsync(new IdentityRole(role)); logger.LogInformation($角色 {role} 创建成功); } } // 创建初始管理员账号 var adminEmail adminexample.com; var adminUser await userManager.FindByEmailAsync(adminEmail); if (adminUser null) { adminUser new ApplicationUser { UserName adminEmail, Email adminEmail, Nickname 系统管理员, EmailConfirmed true }; var result await userManager.CreateAsync(adminUser, Admin123456); if (result.Succeeded) { await userManager.AddToRoleAsync(adminUser, Admin); logger.LogInformation(管理员账号创建成功); } } } }在Program.cs里调用using (var scope app.Services.CreateScope()) { await DbInitializer.InitializeAsync(scope.ServiceProvider); }这里的账号密码是演示配置你在真实项目中务必改成强密码并且记得在部署后立刻修改。种子数据里也不要硬编码生产环境的管理员密码比较稳妥的做法是只创建角色管理员的创建放到部署脚本中人工操作。2.3 登录注册页面的接入与表单校验Identity的后端服务配置好了前端交互页面怎么接Blazor的认证页面本质上是Razor组件你不需要从零写表单逻辑SignInManager和UserManager已经封装好了所有方法。先看登录组件。在.razor文件中注入SignInManagerApplicationUser然后处理表单提交page /login using Microsoft.AspNetCore.Components.Authorization using Microsoft.AspNetCore.Identity inject SignInManagerApplicationUser SignInManager inject NavigationManager Navigation EditForm ModelloginModel OnValidSubmitHandleLogin div label邮箱/label InputText bind-ValueloginModel.Email / /div div label密码/label InputText bind-ValueloginModel.Password typepassword / /div button typesubmit登录/button /EditForm code { private LoginModel loginModel new LoginModel(); private async Task HandleLogin() { var result await SignInManager.PasswordSignInAsync( loginModel.Email, loginModel.Password, isPersistent: true, lockoutOnFailure: true); if (result.Succeeded) { Navigation.NavigateTo(/, forceLoad: true); } else if (result.IsLockedOut) { // 提示用户账号已锁定 } else if (result.RequiresTwoFactor) { // 跳转到双因素验证页面 } else { // 提示用户名或密码错误 } } }PasswordSignInAsync返回的SignInResult包含多种状态Succeeded、Failed、IsLockedOut、RequiresTwoFactor、IsNotAllowed。我见过有人只判断Succeeded或Failed结果用户被锁定了还显示“密码错误”误导用户去重置密码而不是等锁定时间结束。务必把每种状态都区分出来给用户清晰的操作指引。注册组件的逻辑大同小异使用UserManager.CreateAsync完成用户创建var user new ApplicationUser { UserName registerModel.Email, Email registerModel.Email, Nickname registerModel.Nickname }; var result await UserManager.CreateAsync(user, registerModel.Password); if (result.Succeeded) { await SignInManager.SignInAsync(user, isPersistent: false); Navigation.NavigateTo(/); } else { // 遍历 result.Errors 收集错误信息展示给用户 }CreateAsync返回的IdentityResult中的Errors是一个集合里面每一项都有Code和Description。不要只给用户“注册失败”四个字把具体的错误描述展示出来比如“密码必须包含数字”“该邮箱已被注册”。用户体验上的细节往往决定一个产品的基础质量。3. 策略授权与角色权限的实战配置认证跑通之后接下来是授权。Blazor中使用[Authorize]特性配合角色或策略控制访问但很多人不知道策略可以做得非常灵活不只是“管理员”和“普通用户”二选一。3.1 基于角色授权的基础用法最简单的角色授权在Razor组件顶部直接加特性page /admin/dashboard attribute [Authorize(Roles Admin)]或者在路由中指定attribute [Authorize]如果整个页面只允许登录用户访问不加Roles参数即可。如果要限定多个角色用逗号分隔attribute [Authorize(Roles Admin,Manager)]注意这里是“或”的关系满足其中一个角色就可以访问。如果需求的“且”的关系必须同时具备两个角色就需要自定义策略了后面会说。在代码中也可以用AuthorizeView组件做局部控制AuthorizeView RolesAdmin p只有管理员能看到这段内容/p /AuthorizeViewAuthorizeView的本质是订阅认证状态变化当用户的角色信息发生变化时自动重新渲染。这个组件做按钮级别的控制很好用比如“只有管理员才能看到删除按钮”。3.2 自定义授权策略与权限要求现实中的权限需求往往比角色更复杂。比如“订单管理员可以查看所有订单业务员只能查看自己负责区域的订单”这种就不好用简单的角色来表达了。这时候自定义策略是更好的方案。先在Program.cs中注册策略builder.Services.AddAuthorization(options { options.AddPolicy(RequireAdmin, policy policy.RequireRole(Admin)); options.AddPolicy(CanManageOrders, policy { policy.RequireAuthenticatedUser(); policy.RequireClaim(Department, Sales); policy.RequireRole(Admin, Manager, Sales); }); options.AddPolicy(CanOnlyViewOwnData, policy { policy.RequireAuthenticatedUser(); policy.RequireAssertion(context { var userId context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; var requestUserId // 从当前请求上下文获取目标资源的所有者ID return userId requestUserId; }); }); });然后在组件中使用attribute [Authorize(Policy CanManageOrders)]策略授权的强大之处在于灵活组合。RequireAssertion甚至可以写任意的业务判断逻辑比如根据用户的积分等级决定是否放行高级功能或者根据用户的部门、职级、项目归属做动态判断。注意这里有一个非常关键的坑策略的RequireAssertion中不要依赖从数据库查询的实时数据来做判断因为授权中间件在每次请求时都会执行策略如果有高频访问的接口每次都查数据库会带来性能问题。正确做法是登录时把稳定不变的权限信息写入Claim策略只消费Claim需要实时数据的权限判断放到业务层去做。3.3 JWT Token的补充说明与Refresh Token机制如果你是WebAssembly模式最终大概率要上JWT。JWT的配置和Identity是兼容的。在Program.cs中Identity服务之后注册JWT Bearer认证builder.Services.AddAuthentication(options { options.DefaultAuthenticateScheme JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options { options.TokenValidationParameters new TokenValidationParameters { ValidateIssuer true, ValidateAudience true, ValidateLifetime true, ValidateIssuerSigningKey true, ValidIssuer builder.Configuration[Jwt:Issuer], ValidAudience builder.Configuration[Jwt:Audience], IssuerSigningKey new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration[Jwt:Key])) }; });登录成功后签发Tokenprivate async Taskstring GenerateJwtToken(ApplicationUser user) { var userClaims await UserManager.GetClaimsAsync(user); var roles await UserManager.GetRolesAsync(user); var roleClaims roles.Select(r new Claim(ClaimTypes.Role, r)); var claims new ListClaim { new Claim(JwtRegisteredClaimNames.Sub, user.Id), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.Email, user.Email) } .Union(userClaims) .Union(roleClaims); var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config[Jwt:Key])); var creds new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var expires DateTime.Now.AddMinutes(30); var token new JwtSecurityToken( issuer: _config[Jwt:Issuer], audience: _config[Jwt:Audience], claims: claims, expires: expires, signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); }JWT方案一定要设计Refresh Token机制。Access Token的过期时间不要设太长15到30分钟比较合理Refresh Token设7天存储在HttpOnly的Cookie中避免XSS攻击窃取。客户端拿到Access Token后放在内存中页面刷新后从API重新获取不要直接存localStorage。4. Blazor前端安全防线与实际开发中的要点Blazor的认证授权有一个很容易被忽视的组件AuthenticationStateProvider。这个组件负责向UI层提供当前用户的认证状态和Claims信息。Blazor Server模式有现成的实现但WebAssembly模式需要手动写一个自定义Provider而且实现不好的话会出现页面闪烁、登录状态不刷新等问题。4.1 自定义AuthenticationStateProvider的实践如果你用WebAssembly模式或者混合模式强烈建议自定义一个AuthenticationStateProvider使得UI层能感知登录状态的实时变化。public class CustomAuthStateProvider : AuthenticationStateProvider { private readonly ILocalStorageService _localStorage; private readonly HttpClient _http; public CustomAuthStateProvider(ILocalStorageService localStorage, HttpClient http) { _localStorage localStorage; _http http; } public override async TaskAuthenticationState GetAuthenticationStateAsync() { var token await _localStorage.GetItemAsyncstring(authToken); if (string.IsNullOrEmpty(token)) { return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); } var identity new ClaimsIdentity(ParseClaimsFromJwt(token), jwt); var user new ClaimsPrincipal(identity); return new AuthenticationState(user); } public void NotifyUserAuthentication(string token) { var identity new ClaimsIdentity(ParseClaimsFromJwt(token), jwt); var user new ClaimsPrincipal(identity); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user))); } public void NotifyUserLogout() { var anonymous new ClaimsPrincipal(new ClaimsIdentity()); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(anonymous))); } }AuthenticationStateProvider在Blazor Server模式中是内置的它会从HttpContext中解析用户的Claims。但如果你把AuthenticationStateProvider单独抽象成接口并在所有组件中注入更便于测试和跨模式复用。有一个细节继承AuthenticationStateProvider时务必重写GetAuthenticationStateAsync并支持缓存否则每次组件刷新或者路由切换都会触发解析逻辑频繁解析JWT会有性能损耗。我见过一个项目在GetAuthenticationStateAsync里每次都调用API去刷新Token页面切换慢到怀疑人生。4.2 前端权限控制与API后端校验的配合在Blazor组件里做权限控制用的是AuthorizeView和[Authorize]特性但请记住这只是“前端显示控制”真正的安全校验永远在后端。举个例子在WebAssembly模式下你在前端隐藏了“删除订单”按钮如果攻击者直接构造一个HTTP请求调用/api/order/delete/1没有后端校验的话数据照样会被删除。因此所有涉及敏感操作的API端点都要加上[Authorize]或对应的策略。我推荐的做法是Program.cs里对全局路由加一个默认的FallbackPolicy保证未明确标注的API也有最低限度的认证要求builder.Services.AddAuthorization(options { options.FallbackPolicy new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); });这样设置之后哪怕你新写了一个API忘记加[Authorize]至少需要登录用户身份才能访问不会出现裸奔的匿名接口。如果要允许匿名访问特定接口比如登录接口本身在控制器或端点上加[AllowAnonymous]即可。这个兜底策略是少踩坑的关键配置。4.3 数据保护与Token存储的安全注意事项Blazor Server模式下认证票据由ASP.NET Core数据保护机制加密这部分默认帮你处理好了。但生产环境中要注意配置数据保护密钥的持久化存储否则重启应用后所有用户都要重新登录。在Program.cs中配置builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(D:\keys)) .SetApplicationName(YourAppName) .SetDefaultKeyLifetime(TimeSpan.FromDays(90));密钥存储位置视你的部署环境决定。单机部署可以直接用文件系统负载均衡多实例部署时需要把密钥存到共享位置比如Redis或数据库并确保所有实例使用相同的ApplicationName否则用户的登录状态在不同服务器之间无法互通。WebAssembly模式下如果你使用JWTToken的存储位置值得认真权衡存储位置优点缺点localStorage持久化刷新不丢失易受XSS攻击窃取sessionStorage关闭浏览器即失效新开标签页不共享内存变量最安全XSS无法读取页面刷新就丢失HttpOnly CookieXSS无法读取可设Secure和SameSite需要额外的防CSRF配置我的建议是组合使用Access Token放内存Refresh Token放HttpOnly Cookie这样兼顾安全性和用户体验。如果你的项目暂时不需要那么高的安全等级也要至少避免把Token直接放localStorage后不做任何过期检查。5. 常见问题与排查技巧实录最后整理我在实际开发中遇到的高频问题。这些问题小而烦但排查起来往往要花不少时间。5.1 登录成功后页面一直在登录态之间闪跳这个现象通常出现在Blazor Server项目升级或重构之后。排查思路如下第一步确认Program.cs中的认证中间件注册顺序是否正确。中间件的顺序在ASP.NET Core中极其重要UseAuthentication与UseAuthorization必须在UseRouting之后、UseEndpoints之前。app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages(); app.MapBlazorHub();第二步检查AuthenticationStateProvider是否被覆盖注册。如果你自己写了一个Provider并在DI容器中覆盖了内置实现很可能破坏了原有的认证解析链路。第三步检查Cookie的SameSite属性。如果前端页面和后端API跨域部署Cookie的SameSite设置不当会导致Cookie无法携带表现为登录后跳转回登录页。5.2 Identity生成的表过多是否每个表都用得上ASP.NET Core Identity默认会创建以下表AspNetUsers、AspNetRoles、AspNetUserClaims、AspNetRoleClaims、AspNetUserRoles、AspNetUserLogins、AspNetUserTokens。不少新手看到七张表有点慌担心影响性能。实际上不用太担心。AspNetUsers是用户主表核心数据都在这里AspNetRoles是角色表对应你的角色体系AspNetUserRoles维护用户和角色的多对多关系AspNetUserClaims存用户自定义Claim比如Nickname、AvatarUrlAspNetUserLogins存储外部登录Provider的信息Google、微信等AspNetUserTokens存储Token数据用于密码重置、邮箱验证、Refresh Token等AspNetRoleClaims存储角色级别的Claim如果某些功能你用不到比如外部登录AspNetUserLogins就基本是空的。但表的存在本身不影响性能除非你不对它建索引而还经常查。所以不建议为了“精简”去删除Identity的表结构默认设计已经足够合理。5.3 页面级权限控制与数据级权限控制的取舍授权不只是控制页面能不能访问更多业务场景需要对某个“实体”进行数据级授权。比如“用户A只能查看自己创建的订单”“经理可以看本部门所有订单”这属于数据级授权不是在组件上加[Authorize]能解决的。我的建议是页面/API级别的权限用Authorize特性和策略解决数据级别的权限在业务逻辑中显式校验。不要在组件里过滤数据列表一旦列表被过滤按钮的可见性逻辑就会变得极其复杂后续功能变更时非常容易出现权限漏洞。校验时可以用IOwnerAuthorizationService这样的辅助服务public interface IOwnerAuthorizationService { Taskbool CanAccessResourceAsync(ClaimsPrincipal user, string resourceOwnerId); } public class OwnerAuthorizationService : IOwnerAuthorizationService { public Taskbool CanAccessResourceAsync(ClaimsPrincipal user, string resourceOwnerId) { var currentUserId user.FindFirstValue(ClaimTypes.NameIdentifier); return Task.FromResult(currentUserId resourceOwnerId); } }在服务层中注入这个服务执行修改或删除操作前先校验资源归属权。这也是我在实践中觉得收益最高的一个模块业务一复杂你就明白它的价值。5.4 排查授权异常时的有效方法遇到“明明已登录但页面提示无权限访问”这类问题排查的核心是确认用户实际拿到了哪些Claims。有一个非常实用的方法在需要排查的页面里临时渲染当前用户的所有Claimsattribute [Authorize] inject AuthenticationStateProvider AuthStateProvider foreach (var claim in (await AuthStateProvider.GetAuthenticationStateAsync()).User.Claims) { pclaim.Type: claim.Value/p }这个临时页面能帮你快速判断角色Claim是否缺失、自定义Claim是否过期、Claim的Type是否拼错拼写错误是超高频出错点。比如ClaimTypes.Role和role不一定是一样的JWT中的角色Claim默认Type是role而ASP.NET Core默认使用ClaimTypes.Role来匹配角色两边不对应就会出现“有角色但授权失败”的诡异问题。遇到这种问题你要检查Token生成和验证时的Claim映射配置options.MapInboundClaims false; // 是否自动重映射Claim类型写在实际项目中的体会认证授权这块很多人最初觉得是“配置一下就能跑”的东西但实际做下来涉及的点非常多认证方案选型、Identity的配置细节、策略的灵活性、前端UI的限制、后端接口的兜底验证、Token存储的安全性甚至数据库索引和数据保护密钥的持久化。每一步都有讲究而且只有踩过坑才会真正记住。我个人的经验是先明确项目形态是Server端为主还是WebAssembly为主再决定认证方案。不要盲目追求所谓“最优方案”适合项目现状和团队能力的技术就是好技术。权限模型的抽象不必一开始就做到极致但一定要留好扩展空间策略式授权比硬编码角色判断更容易扩展前期数据模型上预留字段后期加权限维度时就不用改表结构。这是我在多个全栈项目中反复验证过的一条原则。如果你正在做一个Blazor全栈项目把认证授权的每一层都梳理清楚把前端和API两侧的校验都落到实位它会成为你后续所有功能迭代最稳固的基石而不是动不动就要推翻重来的隐患。
返回列表