目录
-
- [一、ASP.NET Identity 核心概念:先搞懂 "是什么"](#一、ASP.NET Identity 核心概念:先搞懂 “是什么”)
- [二、环境准备:先搭好 "地基"](#二、环境准备:先搭好 “地基”)
- [三、核心功能实战:代码 + 解析](#三、核心功能实战:代码 + 解析)
- 四、前端视图示例(简化版)
- 五、实战避坑清单(精华总结)
- 六、与读者互动:提问
作为.NET 开发者,身份认证与授权是 Web 应用的 "安全大门",而ASP.NET Identity 作为官方标配,就像小区的智能门禁系统 ------ 用对了省心,用错了处处是坑。本文从实战出发,手把手教你实现注册、登录、密码加密、角色管理核心功能,拆解高频踩坑点,用生活例子讲透抽象概念,新手也能轻松上手。

一、ASP.NET Identity 核心概念:先搞懂 "是什么"
小节:用生活类比,秒懂核心概念
在写代码前,先把抽象的技术概念落地到生活场景,避免从代码直接踩坑:
| 技术概念 | 生活类比 | 核心作用 |
|---|---|---|
| 身份认证(Authentication) | 小区门禁刷脸 / 刷卡 | 验证 "你是谁" |
| 授权(Authorization) | 小区不同楼栋的门禁权限 | 验证 "你能做什么" |
| 密码加密 | 把银行卡密码存在银行的加密库 | 防止明文密码泄露 |
| 角色管理 | 小区业主 / 物业 / 访客身份标签 | 按身份分配不同操作权限 |
小节:核心功能流程图
用流程图梳理完整的身份认证 & 授权流程,避免开发时逻辑混乱:
否
是
无权限
有权限
用户注册
密码加密存储
用户登录
认证是否通过?
返回登录失败
获取用户角色
授权检查
返回403禁止访问
执行用户请求
二、环境准备:先搭好 "地基"
小节:前置条件(避坑第一步)
在写代码前,必须确认环境和依赖,这是新手最容易忽略的坑:
1.框架版本:推荐.NET 6/7/8(Identity 在新版中简化了配置,坑更少)
2.依赖包(NuGet 安装):
bash
Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer # 以SQL Server为例
Install-Package Microsoft.EntityFrameworkCore.Tools
数据库:提前创建空数据库(如IdentityDemoDB)
小节:踩坑点 1 - 依赖版本不匹配
❌ 常见坑:混用不同版本的 Identity 和 EF Core 包(比如 Identity 用 8.0,EF Core 用 7.0),启动时直接报 "类型未找到" 或 "方法不存在"。
✅ 避坑方案:所有相关包统一版本(比如都用 8.0.0),NuGet 安装时勾选 "兼容的版本"。
三、核心功能实战:代码 + 解析
1. 第一步:配置 Identity 上下文(数据库交互核心)
小节:类比理解 ------ 这是 "门禁系统的数据库",存储用户 / 角色信息
csharp
// Models/ApplicationDbContext.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
// 自定义用户类(可扩展字段,比如手机号、昵称)
public class ApplicationUser : IdentityUser
{
// 扩展字段示例:用户真实姓名
public string RealName { get; set; }
// 扩展字段:注册时间
public DateTime RegisterTime { get; set; } = DateTime.Now;
}
// 数据库上下文
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
// 重写OnModelCreating,可自定义表名、字段约束(可选)
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder); // 必须调用,否则Identity表不生成
// 可选:自定义表名(默认是AspNetUsers、AspNetRoles,可改成中文/更简洁的名字)
builder.Entity<ApplicationUser>().ToTable("Sys_Users");
builder.Entity<IdentityRole>().ToTable("Sys_Roles");
builder.Entity<IdentityUserRole<string>>().ToTable("Sys_UserRoles");
}
}
小节:踩坑点 2 - 忘记调用 base.OnModelCreating
❌ 常见坑:重写 OnModelCreating 后,删掉了base.OnModelCreating(builder),导致迁移时不生成 Identity 的核心表(AspNetUsers 等),运行时报 "表不存在"。
✅ 避坑方案:无论是否自定义表结构,都必须先调用基类方法。
2. 第二步:Program.cs 中注册 Identity 服务
小节:类比理解 ------ 这是 "启动门禁系统",配置核心规则
csharp
// Program.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// 1. 配置数据库连接
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// 2. 注册Identity服务(核心配置)
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
// 密码规则配置(类比:小区门禁密码要求------至少8位、含大小写、数字)
options.Password.RequireDigit = true; // 必须包含数字
options.Password.RequiredLength = 8; // 最小长度8
options.Password.RequireNonAlphanumeric = false; // 可选:是否需要特殊字符(比如!@#)
options.Password.RequireUppercase = true; // 必须包含大写字母
options.Password.RequireLowercase = true; // 必须包含小写字母
// 锁定规则(类比:连续输错密码5次,门禁锁定10分钟)
options.Lockout.MaxFailedAccessAttempts = 5; // 最大失败次数
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10); // 锁定时间
options.Lockout.AllowedForNewUsers = true; // 新用户启用锁定
// 用户规则
options.User.RequireUniqueEmail = true; // 邮箱必须唯一
})
.AddEntityFrameworkStores<ApplicationDbContext>() // 指定存储上下文
.AddDefaultTokenProviders(); // 用于密码重置、邮箱验证等令牌生成
// 3. 配置授权(允许基于角色的授权)
builder.Services.AddAuthorization(options =>
{
// 可选:自定义策略(比如要求用户是"管理员"且邮箱已验证)
options.AddPolicy("AdminAndVerified", policy =>
{
policy.RequireRole("Admin"); // 要求Admin角色
policy.RequireClaim("EmailConfirmed", "True"); // 要求邮箱已验证
});
});
// 4. 添加MVC控制器和视图(如果是MVC项目)
builder.Services.AddControllersWithViews();
var app = builder.Build();
// 中间件配置
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// 必须先启用认证,再启用授权(顺序不能乱)
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
小节:踩坑点 3 - 认证 / 授权中间件顺序错误
❌ 常见坑:把app.UseAuthorization()放在app.UseAuthentication()前面,导致登录后依然提示 "未授权"。
✅ 避坑方案:记住口诀 ------先认证(确认身份),后授权(判断权限)。
小节:踩坑点 4 - 密码规则配置过严 / 过松
❌ 常见坑 1:默认密码规则太严(要求特殊字符),测试时频繁输错,降低开发效率;
❌ 常见坑 2:生产环境把密码规则设为全 false,导致密码只有 1 位,安全漏洞;
✅ 避坑方案:
- 开发环境:暂时关闭特殊字符要求(RequireNonAlphanumeric = false);
- 生产环境:严格配置(长度≥8、含大小写 + 数字),并配合密码强度检测。
3. 第三步:编写控制器实现核心功能
(1)注册功能
csharp
// Controllers/AccountController.cs
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
// 依赖注入UserManager(用户管理)、SignInManager(登录管理)
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
// 注册页面
[HttpGet]
public IActionResult Register()
{
return View();
}
// 注册提交(POST)
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
// 创建用户对象
var user = new ApplicationUser
{
UserName = model.Email, // 用邮箱作为用户名(更符合用户习惯)
Email = model.Email,
RealName = model.RealName,
RegisterTime = DateTime.Now
};
// 创建用户(自动加密密码)
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// 可选:注册后自动登录
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
// 注册失败:把错误信息添加到ModelState,返回页面显示
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// 验证失败/注册失败,返回注册页面
return View(model);
}
// 注册视图模型(接收前端参数)
public class RegisterViewModel
{
[Required(ErrorMessage = "邮箱不能为空")]
[EmailAddress(ErrorMessage = "邮箱格式错误")]
public string Email { get; set; }
[Required(ErrorMessage = "真实姓名不能为空")]
[MaxLength(20, ErrorMessage = "姓名最长20个字符")]
public string RealName { get; set; }
[Required(ErrorMessage = "密码不能为空")]
[DataType(DataType.Password)]
[Display(Name = "密码")]
// 自定义密码验证(和Identity配置保持一致)
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$",
ErrorMessage = "密码至少8位,包含大小写字母和数字")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "确认密码")]
[Compare("Password", ErrorMessage = "两次输入的密码不一致")]
public string ConfirmPassword { get; set; }
}
}
小节:密码加密 ------Identity 的 "隐形保护"
💡 核心原理:调用_userManager.CreateAsync时,Identity 会自动使用PBKDF2 算法加密密码(不是 MD5 这种简单哈希),存储在数据库中的是加密后的哈希值,即使数据库泄露,黑客也无法还原明文密码。
🚫 踩坑点:新手手动加密密码后再传给CreateAsync,导致密码被双重加密,登录时提示 "密码错误"。
✅ 正确做法:直接传明文密码给CreateAsync,让 Identity 自动处理加密。
(2)登录功能
csharp
// AccountController中新增登录方法
// 登录页面
[HttpGet]
public IActionResult Login(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
// 登录提交(POST)
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// 登录验证(IsPersistent = model.RememberMe 对应"记住我")
var result = await _signInManager.PasswordSignInAsync(
model.Email, // 用户名(这里用邮箱)
model.Password, // 密码
model.RememberMe, // 是否记住登录
lockoutOnFailure: true); // 失败次数达到阈值时锁定账户
if (result.Succeeded)
{
// 登录成功,跳转到原请求页面(比如用户想访问/admin,登录后直接跳过去)
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
if (result.IsLockedOut)
{
ModelState.AddModelError(string.Empty, "账户已被锁定,请10分钟后重试");
}
else
{
ModelState.AddModelError(string.Empty, "邮箱或密码错误");
}
}
// 验证失败/登录失败,返回登录页面
return View(model);
}
// 登录视图模型
public class LoginViewModel
{
[Required(ErrorMessage = "邮箱不能为空")]
[EmailAddress(ErrorMessage = "邮箱格式错误")]
public string Email { get; set; }
[Required(ErrorMessage = "密码不能为空")]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "记住我")]
public bool RememberMe { get; set; }
}
小节:踩坑点 5 - 登录时 "记住我" 功能失效
❌ 常见坑:勾选 "记住我" 后,关闭浏览器再打开,依然需要重新登录。
✅ 避坑方案:
1.确保IsPersistent参数设为model.RememberMe;
2.检查 Cookie 配置(默认有效期 14 天,无需额外配置);
3.开发环境不要用无痕模式测试(无痕模式会自动清除 Cookie)。
(3)角色管理功能
csharp
// Controllers/RoleController.cs(需添加[Authorize(Roles = "Admin")],仅管理员可访问)
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
[Authorize(Roles = "Admin")] // 仅Admin角色可访问
public class RoleController : Controller
{
private readonly RoleManager<IdentityRole> _roleManager;
private readonly UserManager<ApplicationUser> _userManager;
public RoleController(
RoleManager<IdentityRole> roleManager,
UserManager<ApplicationUser> userManager)
{
_roleManager = roleManager;
_userManager = userManager;
}
// 1. 创建角色
[HttpGet]
public IActionResult CreateRole()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateRole(CreateRoleViewModel model)
{
if (ModelState.IsValid)
{
var role = new IdentityRole { Name = model.RoleName };
var result = await _roleManager.CreateAsync(role);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
return View(model);
}
// 2. 给用户分配角色
[HttpGet]
public IActionResult AssignRole()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AssignRole(AssignRoleViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(model.UserEmail);
if (user == null)
{
ModelState.AddModelError(string.Empty, "用户不存在");
return View(model);
}
// 先移除用户已有角色(可选,避免重复分配)
var currentRoles = await _userManager.GetRolesAsync(user);
await _userManager.RemoveFromRolesAsync(user, currentRoles);
// 分配新角色
var result = await _userManager.AddToRoleAsync(user, model.RoleName);
if (result.Succeeded)
{
ViewData["Message"] = "角色分配成功";
return View();
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
return View(model);
}
// 创建角色视图模型
public class CreateRoleViewModel
{
[Required(ErrorMessage = "角色名称不能为空")]
[MaxLength(20, ErrorMessage = "角色名称最长20个字符")]
public string RoleName { get; set; }
}
// 分配角色视图模型
public class AssignRoleViewModel
{
[Required(ErrorMessage = "用户邮箱不能为空")]
[EmailAddress(ErrorMessage = "邮箱格式错误")]
public string UserEmail { get; set; }
[Required(ErrorMessage = "角色名称不能为空")]
public string RoleName { get; set; }
}
}
小节:踩坑点 6 - 角色名称大小写问题
❌ 常见坑:创建角色时用 "admin",分配时用 "Admin",导致提示 "角色不存在"(Identity 角色名称大小写敏感)。
✅ 避坑方案:
统一角色名称大小写(比如全部大写 / 小写);
分配角色前先将角色名称转为统一格式(如model.RoleName.ToLower())。
4. 第四步:生成数据库迁移(关键步骤)
小节:类比理解 ------ 这是 "给门禁系统建数据库表"
bash
// 1. 生成迁移文件(命名为InitialCreate)
Add-Migration InitialCreate
// 2. 应用迁移到数据库(创建表)
Update-Database
小节:踩坑点 7 - 迁移失败 / 表不生成
❌ 常见坑:
1.连接字符串错误(比如数据库名称写错、服务器地址不对);
2.没有安装 EF Core Tools 工具包;
3.上下文类没有正确继承IdentityDbContext;
✅ 避坑方案:
4.检查 appsettings.json 中的连接字符串:
json
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=IdentityDemoDB;Trusted_Connection=True;TrustServerCertificate=True;"
}
5.确认已安装Microsoft.EntityFrameworkCore.Tools;
6.迁移前先编译项目(Ctrl+Shift+B),确保无语法错误。
四、前端视图示例(简化版)
注册视图(Views/Account/Register.cshtml)
html
@model AccountController.RegisterViewModel
<h2>用户注册</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Register">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="RealName"></label>
<input asp-for="RealName" class="form-control" />
<span asp-validation-for="RealName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword"></label>
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">注册</button>
</form>
</div>
</div>
登录视图(Views/Account/Login.cshtml)
html
@model AccountController.LoginViewModel
<h2>用户登录</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Login">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label asp-for="RememberMe">
<input asp-for="RememberMe" />
@Html.DisplayNameFor(m => m.RememberMe)
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
五、实战避坑清单(精华总结)
1.版本坑: Identity 和 EF Core 包版本必须统一,否则启动报错;
2.顺序坑: 认证中间件(UseAuthentication)必须在授权中间件(UseAuthorization)前面;
3.配置坑: 重写 OnModelCreating 时必须调用 base 方法,否则不生成 Identity 表;
4.密码坑: 不要手动加密密码,交给 Identity 自动处理;
5.角色坑: 角色名称大小写敏感,建议统一格式;
6.迁移坑: 迁移前检查连接字符串和上下文继承关系;
7.记住我坑: 无痕模式测试 "记住我" 功能会失效,用普通模式测试。
六、与读者互动:提问
互动提问:
1.你在项目中是否扩展过 Identity 的用户字段?扩展了哪些?
2.除了角色管理,你还用过 Identity 的哪些高级功能(比如邮箱验证、密码重置)?
3.你觉得 Identity 对比 JWT 认证,各自的优势是什么?
欢迎在评论区留下你的答案和问题,我会一一回复!
总结
关键点回顾
1.ASP.NET Identity 核心是实现 "认证(验身份)+ 授权(验权限)",核心依赖UserManager、SignInManager、RoleManager;
2.开发时需重点避坑:版本统一、中间件顺序、密码加密自动处理、角色名称大小写统一;
3.数据库迁移是关键步骤,需确保上下文配置正确、连接字符串有效。
希望本文能帮你彻底搞懂ASP.NET Identity 核心功能,避开那些新手常踩的坑!如果觉得有用,欢迎点赞、收藏、转发~