首页 / 资讯中心 / 文章详情

ASP.NET Core Identity 完全指南:从注册登录到角色权限实战

ASP.NET Core Identity 完全指南:从注册登录到角色权限实战 ★ FEATURED ARTICLE
大概在接到第三个需要做用户登录的Web项目时我决定认真把ASP.NET Core Identity整套机制搞明白。之前那个项目用的是Session加一张自定义用户表注册、登录、改密码全部手写听起来工作量不大真正上线后才发现密码重置、锁定策略、防暴力破解、角色权限每一项都在后面等着你。后来用Identity之后最大的感受是这套东西不是把登录框画出来给你而是把身份验证这个领域几乎所有的默认选择都替你做好了。这篇文章对应asp.net core系列的第13篇主角就是Identity身份验证。我会从最基础的搭建开始讲清楚它解决的问题、核心概念、常见的坑以及怎么把默认的注册登录改造成符合自己业务的样子。不管你是刚接触.NET还是已经写过几套后台系统这篇文章应该能帮你少走不少弯路。1. 项目概述什么样的项目才需要 Identity 这套身份体系1.1 从“自己写一个登录模块”开始讲起很多项目最开始对用户系统的需求就一句话能注册、能登录、能退出。这种需求量级之下确实写一个UserController加一张用户表就能应付。我见过不少项目就是这么干的Session里存一个UserId密码用MD5哈希一下甚至还有存明文密码的。当时觉得挺美代码简单跑得快直到需求开始膨胀。“密码要支持找回”“同一个账号五分钟内连续输错五次要锁定”“后台管理员和普通user要分权限”“运营要看到用户最近登录时间”“需要绑定第三方账号登录”。这些需求是一个接一个来的不是一次性到位。等你把Session方案改造成能支撑这些功能的系统你会发现实现方式和踩坑经验加起来已经足够写出当初那套Identity了。那为什么不直接用Identity呢大部分人的顾虑是黑盒恐惧。你用别人封装好的框架出了问题不知道它内部做了什么调试起来心里没底。但反过来想身份验证这件事本身是一个高度标准化的领域用户存储、密码哈希、会话Cookie、角色权限这些环节的安全设计和最佳实践都是被反复验证过的。自己从零写恰恰是把漏洞重新踩一遍的过程。1.2 Identity 到底帮你省掉了什么Identity是ASP.NET Core官方提供的一套身份验证与管理框架。它不是单纯的登录组件而是一整套围绕“用户身份”的解决方案。它长期被误解成“一个现成的登录页”实际上它提供的是身份领域的原子能力页面只是可选的UI外壳。从功能维度拆开看Identity至少解决了这样几件事用户存储用户、角色、用户声明Claim等表结构已经设计好配合Entity Framework Core直接映射成数据库表不用自己设计用户表结构。密码安全默认使用PBKDF2算法加随机盐做密码哈希自动做多次迭代而不是简单Hash暴力破解成本很高。会话管理Cookie认证中间件负责登录态签发、校验、过期刷新开发者不需要手动操作Session。安全默认策略密码长度、复杂度、账户锁定、登录失败次数这些安全策略都有默认值也可以按业务调整。可扩展UI通过脚手架可以生成完整的注册、登录、重置密码页面UI风格可以接着改。高级能力角色、Claim授权、双因素认证、外部OAuth登录等功能都内置支持。为了直观对比自写方案和Identity的差别我习惯用这样一张表来说服团队伙伴能力点自写Session用户表ASP.NET Core Identity用户表设计自己建表字段随时缺表结构已设计好扩展靠继承密码哈希容易写成MD5、SHA1或明文默认PBKDF2加盐算法可配置登录状态Session或自写CookieCookie认证中间件加密签发锁定策略基本没人实现开个配置项就有角色权限自己设计关联表Role和UserRole表内置外部登录每个平台都要手接AddGoogle/AddGitHub等扩展方法重置密码自己写Token逻辑内置Token生成与验证这张表不是说自写方案一定不行而是说在绝大多数业务里Identity的默认设计考虑了更多边界场景这些场景往往是在系统被攻击之后才会想起的。1.3 什么时候不建议硬套 Identity说了这么多Identity的好处也要泼一盆冷水。有些场景确实不适合强行套它。如果你的项目就是一个纯内部工具两三个账号不需要远程登录不需要密码找回不需要角色权限也没有任何被公开访问的风险那用配置项里写死账号密码的方式完全没有问题引入Identity反而把事情搞复杂了。如果公司已经有一个统一的认证中心所有系统都要通过它做单点登录那接入SSO协议才是正路而不是每个子系统都建一套独立用户库。还有一种情况是团队对底层的认证机制完全没概念只想快速出一个登录功能又要求把页面改得面目全非。这时候Identity虽然能扩但学习成本会让项目进度变得很难看。我的建议是框架还是要学但“要不要在一开始引入”看项目阶段先登录后用Identity托管用户也是一个选择。2. 二十分钟搭出一个最小可用 Identity 环境2.1 准备开发环境与安装 NuGet 包先说一下演示环境。我用的是.NET 8开发工具用VS Code或者Visual Studio都可以。Identity不是一个独立服务它是一组NuGet包所以搭建的第一步是创建项目并引入合适的包。创建MVC项目dotnet new mvc -n StudyIdentity cd StudyIdentity然后安装Identity相关的包。我这里为了本地演示方便数据库用SQLite你正式开发可以换回SQL Server、PostgreSQL或者MySQL连接配置方式基本一样。dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.Sqlite dotnet add package Microsoft.EntityFrameworkCore.Design解释一下这三个包各自的作用。第一个包提供了Identity的核心类型和EF Core集成包括IdentityUser、IdentityRole、IdentityDbContext这些基类。第二个SQLite包是数据库驱动本地演示零配置最舒服。第三个Design包是给命令行迁移用的没有它没法执行dotnet ef migrations。2.2 配置 AppDbContext 与 Identity 服务Identity需要依赖一个DbContext来操作数据库所以先建一个AppDbContext类让它继承IdentityDbContext。IdentityDbContext是一个已经内置了用户、角色、Claims等实体映射的特殊DbContext这比从零建表省了非常多事。项目里新建Data/AppDbContext.csusing Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace StudyIdentity.Data; public class AppDbContext : IdentityDbContextIdentityUser { public AppDbContext(DbContextOptionsAppDbContext options) : base(options) { } }接下来在Program.cs里注册Identity服务。这是整个配置的核心区域我直接给出一个最小的可运行版本var builder WebApplication.CreateBuilder(args); builder.Services.AddDbContextAppDbContext(options options.UseSqlite(Data Sourceapp.db)); builder.Services.AddDefaultIdentityIdentityUser(options { options.SignIn.RequireConfirmedAccount false; options.Password.RequiredLength 6; }) .AddEntityFrameworkStoresAppDbContext(); builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); var app builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapDefaultControllerRoute(); app.MapRazorPages(); app.Run();有几个地方要特别说明。AddDefaultIdentity ()负责把用户管理器、登录管理器、角色管理器等核心服务注册进去同时配置Cookie认证方案。第二个泛型参数指定我们的用户类型现在用的是框架内置的IdentityUser。如果后续要扩展字段把这里的IdentityUser替换成自定义的ApplicationUser即可。AddEntityFrameworkStores ()告诉Identity用户存储用哪个DbContext。这个泛型必须和上面AppDbContext的类型参数对应。为什么还要加AddRazorPages()和MapRazorPages()因为Identity的默认登录注册页面是用Razor Pages方式发布出来的不注册这个服务页面就不能被访问。还有一句经常被忽略的代码app.UseAuthentication()。它必须放在UseAuthorization()之前。认证和授权是两件事认证在前面把用户身份识别出来授权在后面判断“这个身份能不能访问当前资源”。顺序反了整个权限判断就是个笑话后面常见问题里我会详细说这个坑。2.3 跑一次迁移看默认 UI 长什么样配置写好了下面把数据库建出来。先确认一下EF工具是否安装了如果没有先装全局工具dotnet tool install --global dotnet-ef然后执行迁移和更新数据库dotnet ef migrations add InitialIdentity dotnet ef database update跑起来之后直接访问https://localhost:5001/Identity/Account/Login就能看到登录页/Identity/Account/Register是注册页。这些页面来自一个叫Identity UI的Razor类库你不需要自己写HTML框架把默认模板都带好了。这个默认UI非常朴素但功能是完整的注册、登录、退出、忘记密码、确认邮箱、双因素验证的入口都做了。你用它做内网管理后台的登录页都没问题只是不漂亮而已。打开数据库看一下会发现生成了大量AspNet开头的表。很多第一次接触Identity的人会被表的数量吓到其实每一张都有明确用途表名存放内容典型字段AspNetUsers用户账号主体UserName、Email、PasswordHashAspNetRoles角色定义RoleNameAspNetUserRoles用户与角色多对多关系UserId、RoleIdAspNetUserClaims用户的声明键值对ClaimType、ClaimValueAspNetRoleClaims角色上绑定的声明ClaimType、ClaimValueAspNetUserLogins外部登录账号映射LoginProvider、ProviderKeyAspNetUserTokens密码重置、双因子等临时令牌TokenName、TokenValueAspNetUsers表里的PasswordHash字段建议你去点开看一眼然后再回想一下当年自己项目里用户表的密码字段差别一目了然。2.4 登录流程里到底发生了什么默认登录页面好用是好用但建议你在用它跑通第一次注册登录之后停下来想一想整个流程里系统都干了什么。心里有这条链路后面调试才不至于抓瞎。一次完整的登录流程是这样的用户提交用户名和密码后SignInManager收到请求调用PasswordSignInAsync方法。它先用用户名找到对应的AspNetUsers记录然后取出PasswordHash字段用同样的哈希算法和盐对提交的密码重新计算比对是否一致。这一步通过之后系统会构建一个ClaimsPrincipal对象里面装着用户的Id、UserName、以及用户绑定的角色Claims和自定义Claims。接着Cookie认证中间件把ClaimsPrincipal序列化用数据保护机制加密成一个Cookie返回给浏览器。浏览器后续每次请求都会带上这个Cookie中间件解密后重新还原ClaimsPrincipal赋给HttpContext.User。你的Controller通过User属性或者[Authorize]标签就能拿到当前用户信息。整个过程里开发者实际上不需要碰Session也不需要手动管理Cookie的加密解密。逻辑上要记住的是Cookie里不是存一个UserId就完事它存的是整个身份声明集合。3. 把核心概念拆开User、Role、Claim 与 Cookie 鉴权3.1 Authentication 和 Authorization两件事别混在一起很多入门者把“登录”和“权限”混成一件事情调试问题时经常找错方向。我习惯用坐飞机的流程来解释你到机场出示身份证工作人员确认这是不是你本人这一步是Authentication叫身份验证。你通过安检后走到登机口工作人员看你登机牌上头等舱标识决定能不能让你进头等舱休息室这一步是Authorization叫授权。换到ASP.NET Core里Identity负责的是Authentication。对应到代码就是注册、登录、记住我、退出这些操作。而[Authorize]标签、角色判断、签名Claims检查这些属于Authorization它关心的是“你有没有资格访问这个页面”。所以当你遇到“我登录了为什么还看到403”的时候问题大概率不在登录逻辑而在授权配置。当你遇到“我明明没登录却能访问某个页面”的时候要检查是不是漏掉了[Authorize]标签或者匿名访问配置没关。3.2 User、Role、Claim 在 Identity 里是什么关系这三者理解透了Identity就算入门了。User就是账号本身一张AspNetUsers表字段包括用户名、邮箱、密码哈希、手机号等。Role是角色比如管理员、编辑、访客它本身是一张独立的表用户和角色之间是多对多关系对应AspNetUserRoles表。Claim稍微抽象一点它表示“对主体的一条事实声明”比如“昵称是张三”“生日是1995年1月1日”“是VIP会员”。打个比方User是你的身份证本体Role是证件上印着的“职业/等级”Claim则像一堆小贴纸每张上面写一个键值对贴在你这个账号上。登录时系统会把这些信息打包你访问页面时只需要从打包好的数据里读取不需要每次都查询数据库。在代码里读取方式通常是这样的var userId User.FindFirstValue(ClaimTypes.NameIdentifier); var userName User.Identity.Name; var isVip User.FindFirst(IsVip)?.Value true;这种设计的好处是权限判断变成了一次内存读取而不是每次请求都去数据库查用户的角色和声明。性能是次要的核心是架构清晰。3.3 UserManager、SignInManager、RoleManager 的分工Identity的核心服务集中在三个管理器上你会在Controller或Razor Page里大量注入它们。UserManager负责用户账号生命周期管理包括创建用户、查找用户、确认邮箱、修改密码、锁定用户、添加Claims和角色。SignInManager负责登录态也就是登录、退出、记住我、检查用户是否被允许登录。RoleManager只处理角色本身创建角色、删除角色、给角色绑定Claims。从使用频率来看UserManager用得最多。创建一个用户的典型代码如下var user new IdentityUser { UserName testexample.com, Email testexample.com }; var result await _userManager.CreateAsync(user, Password123!); if (result.Succeeded) { // 创建成功 } else { var errors result.Errors.Select(e e.Description); }CreateAsync的第二个参数是明文密码方法内部会自动完成哈希你不需要自己加密再存进去。这比手写用户表时小心翼翼处理密码的方式省心太多。SignInManager最常见的方法是PasswordSignInAsyncvar result await _signInManager.PasswordSignInAsync( user, input.Password, isPersistent: true, lockoutOnFailure: true);这个方法的第三个参数表示是否记住我第四个参数表示是否在连续失败后锁定账号。内置的锁定策略在AspNetUsers表里有LockoutEnd字段策略配置在AddDefaultIdentity的options里。3.4 Cookie 中间件在一次请求里做了什么跑通一个Identity项目后建议你在浏览器开发者工具里观察一下Cookie。你会看到一个以.AspNetCore.Identity.Application开头的Cookie它的大小通常有好几百字节因为里面存的不只是一个用户ID而是经过加密的整个ClaimsPrincipal对象。一次请求经过中间件时大致经历这样几步用户带Cookie发请求UseAuthentication中间件拦截读取Cookie密文用数据保护API解密并验证签名还原出ClaimsPrincipal赋值给HttpContext.User。然后执行到UseAuthorization中间件它会检查当前请求的端点是否带有[Authorize]标签如果有它再根据User里的Claims、角色信息判断是否放行。这里有一个容易被忽略的性能点Cookie里的Claims在签发之后不会自动和数据库同步。如果用户登录之后变成了管理员但Cookie里没有刷新角色这次请求依然按旧角色判断。打个比方你把“VIP会员”贴纸撕了但身上旧的门票还印着VIP入口保安看的是票不是会员系统。要让角色或者Claims刷新要么等Cookie过期后重新登录要么在中间件里做校验逻辑在OnValidatePrincipal事件里定时刷新。这个进阶点后面会提到。4. 实操记录给用户表扩展字段并定制注册页面4.1 继承 IdentityUser添加自己的业务字段默认的IdentityUser字段够用但真实项目几乎必然要加字段最常见的是昵称、头像、注册时间、生日、性别等。很多人一开始用IdentityUser跑通了后面要加字段时不知道怎么处理其实方法就是继承。新建一个ApplicationUser类让它继承IdentityUserusing Microsoft.AspNetCore.Identity; namespace StudyIdentity.Models; public class ApplicationUser : IdentityUser { public string NickName { get; set; } ; public DateTime CreatedAt { get; set; } DateTime.UtcNow; public string AvatarUrl { get; set; } ; }然后把AppDbContext从IdentityDbContext 改成IdentityDbContext public class AppDbContext : IdentityDbContextApplicationUser { public AppDbContext(DbContextOptionsAppDbContext options) : base(options) { } }Program.cs里的AddDefaultIdentity泛型也要同步换成ApplicationUserbuilder.Services.AddDefaultIdentityApplicationUser(options { options.SignIn.RequireConfirmedAccount false; options.Password.RequiredLength 6; }) .AddEntityFrameworkStoresAppDbContext();这样改完之后AspNetUsers表在下次迁移时会多出NickName、CreatedAt、AvatarUrl这三个列。4.2 生成并定制 Identity 页面默认的注册登录页面藏在Razor类库里业务要修改页面内容时需要先把它生成到项目里。这一步用代码生成器完成。先安装生成工具dotnet tool install --global dotnet-aspnet-codegenerator然后执行dotnet aspnet-codegenerator identity -dc AppDbContext --files Account.Register;Account.Login;Account.Logout --useDefaultUI生成命令执行后项目里会出现一个Areas/Identity/Pages/Account文件夹里面是Register.cshtml、Login.cshtml等文件。这些文件现在成了项目源码的一部分你可以直接改样式、改字段、改后台逻辑。不过要注意这个命令在我实际操作中偶尔会因为版本问题报错。如果生成失败先检查dotnet-ef和dotnet-aspnet-codegenerator的环境变量路径以及项目里是否安装了Microsoft.VisualStudio.Web.CodeGeneration.Design包。4.3 把自定义字段一路从注册页写到数据库注册页的默认代码内部都是IdentityUser现在要改成ApplicationUser并写入新字段。操作分几处看起来琐碎但逻辑链很好理解。先改Register.cshtml页面模型增加一个输入属性public class InputModel { [Required] [EmailAddress] [Display(Name 邮箱)] public string Email { get; set; } ; [Required] [Display(Name 昵称)] [StringLength(20, ErrorMessage 昵称最多20个字符)] public string NickName { get; set; } ; [Required] [StringLength(100, ErrorMessage 密码长度至少6位, MinimumLength 6)] [DataType(DataType.Password)] [Display(Name 密码)] public string Password { get; set; } ; [DataType(DataType.Password)] [Display(Name 确认密码)] [Compare(Password, ErrorMessage 两次密码不一致)] public string ConfirmPassword { get; set; } ; }然后在OnPostAsync里把原来的new IdentityUser改成new ApplicationUser并在创建前赋值var user new ApplicationUser { UserName Input.Email, Email Input.Email, NickName Input.NickName, CreatedAt DateTime.UtcNow }; var result await _userManager.CreateAsync(user, Input.Password); if (result.Succeeded) { _logger.LogInformation(新用户注册用户ID{UserId}, user.Id); if (_userManager.Options.SignIn.RequireConfirmedAccount) { // 邮箱确认流程 } else { await _signInManager.SignInAsync(user, isPersistent: false); return RedirectToPage(/Index); } }Register.cshtml那边加一个输入框div classform-floating mb-3 input asp-forInput.NickName classform-control autocompletenickname aria-requiredtrue / label asp-forInput.NickName昵称/label span asp-validation-forInput.NickName classtext-danger/span /div改完后重新生成一次迁移再更新数据库dotnet ef migrations add AddNickNameToUser dotnet ef database update这样注册时输入的昵称就可以真正存进AspNetUsers表的NickName字段了。4.4 登录后怎么读取这些扩展信息注册时存进去了接下来是登录后怎么用。有三种常见做法各自适用不同场景。第一种拿到当前登录用户的Id后用UserManager从数据库查出完整对象var userName User.Identity?.Name; var user await _userManager.FindByNameAsync(userName); var nickName user?.NickName;这种方式数据最准但每次请求都会多一次数据库查询如果你在页面顶部展示昵称首页高并发时扛不住。第二种在登录时把扩展字段写进Cookie的Claims里var claims new ListClaim { new Claim(NickName, user.NickName) }; await _signInManager.SignInWithClaimsAsync(user, isPersistent: true, claims);之后在任意视图或Controller里直接读取var nickName User.FindFirst(NickName)?.Value;这种方式不查库速度快代价是Cookie变大以及自定义Claims不会因数据库更新而实时刷新。第三种是两种结合初始Claims里放基础信息用户资料变更时显式重新登录或者重新签发。我在实际项目里更推荐这种方式因为它兼顾了性能和准确性。5. 常见问题与排查速查表5.1 现象、原因、解法一张表看清Identity入门之后大多数问题集中在配置、Cookie、迁移这三个方向。我整理了一张速查表是我做技术支持时最常遇见的组合现象常见原因解决思路登录成功后马上跳回登录页UseAuthentication顺序错或Cookie未正确写入检查中间件顺序检查浏览器Set-Cookie一直403 Forbidden未登录访问[Authorize]页面或角色不匹配确认账号登录状态检查角色Claim数据库迁移报“列不存在”改了模型没生成新迁移执行dotnet ef migrations add再update密码重置Token点击后失效服务器重启数据保护密钥丢失配置PersistKeysToFileSystem持久化密钥注册时提示密码不符合策略密码过短或需要特殊字符调整Options.Password的配置项二次登录后角色依然是旧角色用户角色改变但Cookie未刷新手动重新SignIn或定时刷新Claims部署服务器后所有密码Token失效数据保护密钥未共享到服务器配置固定的密钥目录和应用名这个表不是让你背的是要你遇到问题时分门别类去排查。身份验证领域的问题基本都是这个套路先看配置再看数据最后看信使。5.2 登录后无限重定向的排查实录这是我被问过最多的问题没有之一。现象是用户输入正确的账号密码后页面一直在Login和某个受保护页面之间来回跳肉眼看到的就是闪现一下然后继续登录页。最典型的锅是中间件顺序。我见过有人把UseAuthentication写在UseAuthorization后面结果认证环节没走到授权中间件永远觉得用户是匿名身份于是每次都把请求踢回登录页。检查方法是打开Program.cs确认两行代码的顺序app.UseAuthentication(); app.UseAuthorization();第二个常见原因是Cookie没有写出去。本地开发时如果使用HTTP而不是HTTPSIdentity Cookie默认可能不会在非安全连接下发出去浏览器没收到Cookie自然一直认为你是匿名用户。排查时看浏览器开发者工具的Network面板找Login请求的响应头里有没有Set-Cookie没有的话检查Cookie.SecurePolicy配置。本地调试临时改成builder.Services.ConfigureApplicationCookie(options { options.Cookie.SecurePolicy CookieSecurePolicy.SameAsRequest; });第三个奇怪的原因和前端跳转地址有关。如果你的网站部署在反向代理后面外部访问是HTTPS但应用内部看到的是HTTPCookie可能会被丢弃。这时候需要在启动时配置ForwardedHeaders让应用正确识别HTTP协议builder.Services.ConfigureForwardedHeadersOptions(options { options.ForwardedHeaders ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; });5.3 Token 失效、Cookie 过期、数据保护密钥问题围绕Identity有一个很容易被刀的问题本地开发一切正常部署到服务器上就跑不通密码重置、邮箱确认或者“记住我”登录了。最终原因往往是数据保护密钥没有持久化。Cookie加密、密码重置Token、双因子Token底层都依赖ASP.NET Core的数据保护API。默认情况下密钥是存在本机用户目录下的而且服务器重启后会重新生成。同一台机器上Cookie虽然短时间没事但Token这种有效期较短的东西很容易突然就全部失效。部署到生产环境时我建议显式配置密钥持久化路径和应用名builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(/secure/keys)) .SetApplicationName(StudyIdentity_Prod);SetApplicationName这个配置很关键。Identity本身在注册时会设置一个默认应用名如果你有多套环境环境不同Cookie和Token不能互通。显式统一名称后多实例部署时也能共享同一个密钥。这个配置我吃过亏。当时我们发现测试环境的用户反馈“登录几天后自动退出”查了很久最后发现是服务器运维重启了机器密钥丢了。加上持久化配置之后再没出过这个问题。5.4 迁移不小心搞坏数据库的处理思路Identity的迁移操作对新手不友好经常出现“我先改了模型再改了AddDefaultIdentity的泛型却没生成迁移结果运行时报错找不到列”这种情况。数据库结构已经和模型不一致了轻则启动报错重则影响线上数据。最安全的操作习惯是先改模型再生成迁移再更新数据库最后启动项目。顺序颠倒必出问题。如果已经在本地开发环境把数据库搞乱了最简单的恢复手段是直接删掉数据库文件重新来过rm app.db dotnet ef database update但生产环境绝对不能这么干。生产环境有数据正确做法是生成一个带降级逻辑的迁移或者用脚本备份数据后迁移。我遇到过有人直接在服务器上执行了dotnet ef database drop的后果不用多说。Identity相关的表在迁移时通常保留数据是可行的关键是要仔细看迁移文件的Up和Down方法里做了什么。如果只是增加字段这种正向迁移基本不会丢数据。如果设计改了外键关系就要反复确认迁移脚本再执行。6. 接下来可以做的角色、策略授权与 JWT 扩展6.1 角色授权给用户分组再按组控制权限入门阶段搞定注册登录之后下一步需求几乎一定是角色和权限。Identity的角色模型已经很成熟先创建角色再给用户分配后面在Controller上打标签就行。创建角色并分配代码可以写在种子数据里var adminRole new IdentityRole(Admin); await _roleManager.CreateAsync(adminRole); var adminUser await _userManager.FindByEmailAsync(adminexample.com); await _userManager.AddToRoleAsync(adminUser, Admin);Controller上控制权限[Authorize(Roles Admin)] public IActionResult AdminDashboard() { return View(); }如果角色要求很细比如“管理员只能管理内容不能管理用户”我建议把权限细化到Action级别别只停留在Controller级别。也可以用多个角色组合或者干脆用Claim策略。6.2 策略授权把权限判断从 Controller 里抽出来角色授权适合人员分组明确的管理后台但真实业务的权限判断往往更刁钻会员等级、付费状态、用户自己创建的内容才能编辑。这种逻辑如果全写在Controller里会越来越乱。策略授权就是更优雅的方案。在Program.cs里定义策略builder.Services.AddAuthorization(options { options.AddPolicy(VipOnly, policy policy.RequireClaim(IsVip, true)); });然后在控制器上使用[Authorize(Policy VipOnly)] public IActionResult PremiumContent() { return View(); }更复杂的判断还能用RequireAssertion比如“必须是内容创建者本人才能编辑”options.AddPolicy(CanEditArticle, policy policy.RequireAuthenticatedUser() .RequireAssertion(context { var articleOwnerId context.Resource?.GetType() .GetProperty(OwnerId)?.GetValue(context.Resource)?.ToString(); var userId context.User.FindFirstValue(ClaimTypes.NameIdentifier); return articleOwnerId userId; }));策略授权的核心思想是把“谁能做什么”的规则集中定义Controller只关心业务逻辑。规则变了改一处策略即可不用逐个控制器去翻。6.3 外接第三方登录和 JWT 无状态认证做了几个项目之后你会发现第三方登录几乎成了标配。Identity对OAuth的接入支持得很好不是从零对接OAuth协议而是加一个扩展方法的事builder.Services.AddAuthentication() .AddGitHub(options { options.ClientId your-client-id; options.ClientSecret your-client-secret; });配置好开放平台回调地址Identity自动把第三方的身份标识映射到AspNetUserLogins表第一次登录自动创建本地账号后续登录直接绑定。你不用自己处理第三方返回的那一坨Token细节。针对前后端分离的项目Cookie认证不是唯一选择。Identity的用户管理能力依然可以留着但认证方式换成JWT无状态认证。规划是登录接口接收用户名密码用SignInManager验证成功后签发JWT之后的请求通过Authorization头带JWTAddAuthentication改成JwtBearerbuilder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options { options.TokenValidationParameters new TokenValidationParameters { ValidateIssuer true, ValidIssuer StudyIdentity.Server, ValidateAudience true, ValidAudience StudyIdentity.Client, ValidateLifetime true, IssuerSigningKey new SymmetricSecurityKey( Encoding.UTF8.GetBytes(your-signing-key)) }; });这里要强调一点JWT方案同样可以用UserManager来管理用户数据库两者并不冲突。区别只是登录态从“浏览器Cookie自动携带”变成了“前端手动在请求头里带Bearer Token”。6.4 Identity 在单体、前后端分离架构中的定位最后说一下Identity在整个系统中的位置。它不是一定要挂在某个具体的认证协议上它更像一个“身份数据与账号逻辑”的核心库。在传统MVC单体里Identity加Cookie认证是默认组合开发成本最低默认UI都能直接用。在前后端分离的架构里前端是SPA或者小程序Identity提供账号管理能力认证协议换成JWT登录页则完全由前端接管。在微服务架构里常见做法是单独做一个身份认证服务用Identity管理账号体系其他服务不再各自维护用户表而是通过Token里的Claims识别用户身份。选型时把边界想清楚凡是涉及用户注册、密码安全、角色权限的地方都可以用Identity做地基凡是涉及登录态传输协议的地方按部署场景去选Cookie还是JWT。这样架构就不会因为“认证方案”和“身份管理”混在一起而拧巴。说一个我自己的实操细节。每次新项目我第一件事不是急着写业务代码而是先把Identity默认页面生成出来注册一个测试号然后去数据库看一眼AspNetUsers里PasswordHash字段再翻翻Cookie的载荷。这套流程下来基本能确认框架的每个环节工作正常后面就算出问题也知道是业务代码的锅而不是地基的锅。另外一个零成本高回报的小建议在ApplicationUser里加一个CreatedAt字段。字段本身一分钟就加完但后续做用户列表排序、运营日活统计、排查僵尸账号的时候会非常依赖它现在不加过两个月数据已经积累到一定程度再补迁移麻烦程度会翻倍。这种字段看起来不起眼但越早加越省钱。
阅读完成 · 觉得有帮助?
咨询建站