一.什么是标识框架
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.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.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]
[Route("Login")]
public async Task<IActionResult> Login(LoginRequest loginRequest)
{
string userName = loginRequest.UserName;
string password = loginRequest.Password;
// 根据用户名查找用户
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
// 用户不存在,返回 404
return 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);
// 密码正确,登录成功,返回 200
return Ok();
}
else
{
// 密码错误,记录一次失败尝试(用于锁定机制)
var r = await _userManager.AccessFailedAsync(user);
if (!r.Succeeded)
{
// 记录失败信息失败,返回错误
return BadRequest("访问失败信息写入错误!");
}
else
{
// 普通密码错误返回 400
return BadRequest("失败!");
}
}
}
学自杨中科老师,可搭配老师b站视频学习.