在ASP.NET Core WebApi中使用标识框架(Identity)
一.什么是标识框架
Identity框架是微软官方提供的一套完整的身份认证和授权框架,旨在简化用户管理(注册、登录、注销、角色权限)流程。它内置了常见的功能,比如密码哈希、邮箱确认、多因素认证等,同时支持自定义扩展。
因为我们的服务器是不能被随意地访问的,比如有的功能需要注册用户才能访问,有的功能需要管理员才能访问。针对资源的访问限制有两个概念:Authentication与Authorization,即鉴权与授权。
- Authentication:用来对访问者的用户身份进行验证;
- Authorization:用来验证访问者的用户身份是否有对资源进行访问的权限。
通俗来说,Authentication是用来验证“用户是否登录成功”的,Authorization是用来验证“用户是否有权限访问”的。
二.标识框架Identity
大部分系统中都需要通过数据库保存用户、角色等信息,并且需要注册、登录、密码重置、角色管理等功能。ASP.NET Core提供了标识(identity)框架,它采用RBAC(role-based access control,基于角色的访问控制)策略,内置了对用户、角色等表的管理及相关的接口,从而简化了系统的开发。标识框架还提供了对外部登录的支持。
标识框架中提供了IdentityUser<TKey>
、IdentityRole<TKey>
两个实体类型,其中的TKey代表主键的类型,因此IdentityUser<Guid>
就代表使用Guid类型主键的用户实体类。
1.环境搭建
创建ASP.NET Core Web API项目(.net8),并通过NuGet安装
包名称 | 用途 |
---|---|
Microsoft.AspNetCore.Identity.EntityFrameworkCore | ASP.NET Core Identity 用户管理,基于 EF Core |
Microsoft.EntityFrameworkCore.SqlServer | 使用 SQL Server 作为存储数据库(换数据库类型可换包) |
Microsoft.AspNetCore.Authentication.JwtBearer | JWT Bearer 认证中间件 |
Microsoft.EntityFrameworkCore.Tools | 支持数据库迁移命令,比如 Add-Migration 、Update-Database |
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --version 8.0.17
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.EntityFrameworkCore.Tools
2.创建用户实体类User和角色实体类Role
public class User : IdentityUser<long>
{public DateTime CreationTime { get; set; }public string? NickName { get; set; }
}
public class Role : IdentityRole<long>
{
}
IdentityUser中定义了UserName(用户名)、Email(邮箱)、PhoneNumber(手机号)、PasswordHash(密码的哈希值)等属性,我们在User中又添加了CreationTime(创建时间)、NickName(昵称)两个属性(虽然接口本身就有很多基本的字段,但是我们有可能还需要一些其他字段,所以通过继承实现了子类来方便我们添加字段)。
除了IdentityUser和IdentityRole之外,标识框架中还有很多其他实体类,比如IdentityRoleClaim、IdentityUserClaim、IdentityUserLogin、IdentityUserToken等,一般情况下,我们不需要再编写这些实体类的子类。这些实体类有默认的表名,如果需要修改默认的表名或者对实体类进行进一步的配置,我们可以用EF Core中提供的IEntityTypeConfiguration来对实体类进行配置。
3.创建继承自IdentityDbContext的类
这是一个EF Core中的上下文类,我们可以通过这个类操作数据库。IdentityDbContext是一个泛型类,有3个泛型参数,分别代表用户类型、角色类型和主键类型。
public class IdDbContext : IdentityDbContext<User, Role, long>
{public IdDbContext(DbContextOptions<IdDbContext> options): base(options){}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);}
}
我们可以直接通过IdDbContext类来操作数据库,不过标识框架中提供了RoleManager、UserManager等类来简化对数据库的操作,这些类封装了对IdentityDbContext的操作。
4.注册与标识框架相关的服务,并且对相关的选项进行配置
//添加数据包含服务builder.Services.AddDataProtection();builder.Services.AddIdentityCore<User>(options =>{// 密码必须包含数字?options.Password.RequireDigit = false;// 密码必须包含小写字母?options.Password.RequireLowercase = false;// 密码必须包含非字母数字字符?(比如 @、# 等符号)options.Password.RequireNonAlphanumeric = false;// 密码必须包含大写字母?options.Password.RequireUppercase = false;// 密码最小长度options.Password.RequiredLength = 6;// 配置密码重置时,使用哪种 Token 生成器options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;// 配置邮箱确认时,使用哪种 Token 生成器options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;});// 创建 IdentityBuilder 对象,指定用户类型和角色类型,并传入 DI 容器var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), builder.Services);//用 IdDbContext 作为 Identity 存储层(一般是继承自 IdentityDbContext<User, Role, ...> 的自定义类)idBuilder.AddEntityFrameworkStores<IdDbContext>()//注册 内置的默认 Token 生成器.AddDefaultTokenProviders()//把 角色管理服务 RoleManager<Role> 注册进 DI 容器.AddRoleManager<RoleManager<Role>>()//把 用户管理服务 UserManager<User> 注册进 DI 容器.AddUserManager<UserManager<User>>();
在配置文件中写入数据库连接字符串。
"ConnectionStrings": {"Default": "Data Source=.;Database=Customers;User ID=sa;Password=123456;MultipleActiveResultSets=True;"
}
"ConnectionStrings": {"DefaultConnection": "Server=(local);Database=IdDb;Trusted_Connection=True;TrustServerCertificate=True;"}
builder.Services.AddDbContext<IdDbContext>(options =>{options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));});
5.执行数据库迁移
Add-Migration createIdentity
、Update-database
等命令执行EF Core的数据库迁移,然后程序就会在数据库中生成多张数据库表。这些数据库表都由标识框架负责管理,开发人员一般不需要直接访问这些表。
6.编写控制器的代码
我们在控制器中需要对角色、用户进行操作,也需要输出日志,因此通过控制器的构造方法注入相关的服务。
[Route("api/[controller]")][ApiController]public class TestController : ControllerBase{private readonly ILogger<TestController> _logger;private readonly UserManager<User> _userManager;private readonly RoleManager<Role> _roleManager;public TestController(ILogger<TestController> logger, UserManager<User> userManager,RoleManager<Role> roleManager){_logger = logger;_userManager = userManager;_roleManager = roleManager;} }
7.Identity 框架中的方法返回结果:IdentityResult
在 ASP.NET Core Identity 标识框架中,很多用户管理相关的方法,比如:
-
用户创建
-
密码修改
-
密码重置
-
邮箱确认
-
角色添加/删除
都不直接抛异常,而是通过返回一个 IdentityResult
对象来告诉你操作是否成功。
(1)IdentityResult 的特点:
属性/特性 | 说明 |
---|---|
Succeeded | 一个 bool 类型,表示操作是否成功。通常用于快速判断。 |
Errors | 一个 IEnumerable<IdentityError> 类型,存放失败时的详细错误列表。 |
(2)为什么要用 IdentityResult?
因为:
-
Identity 的操作涉及用户数据、密码复杂度、Token 验证、数据库写入等各种可能失败的情况。
-
比起直接抛异常,这种返回对象方式更适合可控错误处理。
(3)IdentityError 结构:
每个失败原因,用一个 IdentityError
对象表示,包含:
属性 | 说明 |
---|---|
Code | 错误码(可用于逻辑判断或多语言翻译) |
Description | 用户可读的错误描述,比如:“Passwords must be at least 6 characters.” |
(4) 示例
var result = await _userManager.ResetPasswordAsync(user, token, newPassword);if (result.Succeeded)
{// 成功处理
}
else
{foreach (var error in result.Errors){Console.WriteLine($"错误码: {error.Code}, 描述: {error.Description}");}
}
8.示例(仅用来熟悉常用API)
(1)编写创建角色和用户的方法。
[HttpPost]
public async Task<IActionResult> CreateUserRole()
{// 判断角色 "Admin" 是否存在bool roleExists = await _roleManager.RoleExistsAsync("Admin");if (!roleExists){// 不存在则创建角色Role role = new Role { Name = "Admin" };var r = await _roleManager.CreateAsync(role);if (!r.Succeeded) // 创建失败,返回 400 和错误信息{return BadRequest(r.Errors);}}// 查找用户名为 "ZhangSan" 的用户User user = await _userManager.FindByNameAsync("ZhangSan");if (user == null){// 不存在则创建新用户,设置用户名、邮箱并确认邮箱user = new User{UserName = "ZhangSan",Email = "3456789@qq.com",EmailConfirmed = true,};var r = await _userManager.CreateAsync(user, "123456"); // 创建用户并设置密码if (!r.Succeeded) // 创建失败返回错误信息{return BadRequest(r.Errors);}// 将用户添加到 "Admin" 角色r = await _userManager.AddToRoleAsync(user, "Admin");if (!r.Succeeded) // 添加角色失败返回错误信息{return BadRequest(r.Errors);}}return Ok(); // 成功返回 200
}
(2)编写处理登录请求的操作方法Login
public record LoginRequest(string UserName, string Password);
[HttpPost]public async Task<IActionResult> Login(LoginRequest loginRequest){string userName = loginRequest.UserName;string password = loginRequest.Password;// 根据用户名查找用户var user = await _userManager.FindByNameAsync(userName);if (user == null){// 用户不存在,返回 404return NotFound($"用户名{userName}不存在!");}// 判断用户是否被锁定(连续登录失败导致)var islocked = await _userManager.IsLockedOutAsync(user);if (islocked){// 用户锁定,返回 400,提示锁定信息return BadRequest("用户已锁定!");}// 验证密码是否正确var success = await _userManager.CheckPasswordAsync(user, password);if (success){//重置访问失败计数await _userManager.ResetAccessFailedCountAsync(user);// 密码正确,登录成功,返回 200return Ok();}else{// 密码错误,记录一次失败尝试(用于锁定机制)var r = await _userManager.AccessFailedAsync(user);if (!r.Succeeded){// 记录失败信息失败,返回错误return BadRequest("访问失败信息写入错误!");}else{// 普通密码错误返回 400return BadRequest("失败!");}}}
学自杨中科老师,可搭配老师b站视频学习.