前言
ASP.NET Core 内置的标识(identity)框架,采用的是 RBAC(role-based access control,基于角色的访问控制)策略,是一个用于管理用户身份验证、授权和安全性的框架。
它提供了一套工具和库,用于管理用户、角色、登录、密码重置、电子邮件确认等功能。
- 用户管理:创建、管理和验证用户,这样你可以轻松操作注册用户、登录、注销、密码重置等功能。
- 角色管理:定义不同的用户角色,并将用户分配到这些角色中,这样你可以更好地控制用户的权限和访问级别。
- 密码策略:框架提供了密码策略功能,允许你定义密码的复杂度要求,例如密码长度、大小写字母、数字等要求。
- 外部登录:框架还支持你使用外部身份验证提供者(如 QQ、微信、微博等)进行身份验证。
- 电子邮件确认:通过电子邮件确认用户注册和密码重置操作,这样注册和修改密码操作更加安全可靠。
今天,我们主要聊聊如何使用 ASP.NET Core 标识(Identity)框架来管理用户和角色,也有涉及到密码策略、电子邮件确认等方面。
- 创建一个 ASP.NET Core webapi 项目,命名为 IdentitySample
- 引用以下 Nuget 包:
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Relational
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
3.修改 appsettings.json,添加数据库连接字符串
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "Default": "Server=(localdb)\\mssqllocaldb;Database=IdentityTestDB;Trusted_Connection=True;MultipleActiveResultSets=true" } }
4.创建用户实体类 User(重点看注释)
using Microsoft.AspNetCore.Identity; // IdentityUser<long> 表示 long 类型主键的用户实体类 // IdentityUser 中定义了 UserName(用户名)、Email(邮箱)、PhoneNumber(手机号)、PasswordHash(密码的哈希值)等属性, // 这里又添加了 CreationTime(创建时间)、NickName(昵称)两个属性。 public class User: IdentityUser<long> { public DateTime CreationTime { get; set; } public string? NickName { get; set; } }
5.创建角色实体类 Role
using Microsoft.AspNetCore.Identity; public class Role: IdentityRole<long> { }
6.创建继承自 IdentityDbContext 的上下文类(重点看注释)
using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; // 使用 Identity 框架要继承 IdentityDbContext // 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); } }
7.打开 Program.cs 文件,向依赖注入容器中注册与标识框架相关的服务,并且对相关的选项进行配置(重点看注释)
using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // 向依赖注入容器中注册与标识框架相关的服务,并对相关的选项进行配置 // ----1. 数据库注入 IServiceCollection services = builder.Services; services.AddDbContext<IdDbContext>(opt => { string connStr = builder.Configuration.GetConnectionString("Default")!; opt.UseSqlServer(connStr); }); // ----2. 数据保护服务注入 // ----数据保护提供了一个简单、基于非对称加密改进的加密API用于确保Web应用敏感数据的安全存储 // ----不需要开发人员自行生成密钥,它会根据当前应用的运行环境,生成该应用独有的一个私钥 services.AddDataProtection(); // ----3. 添加标识框架的一些重要的基础服务 services.AddIdentityCore<User>(options => { options.Password.RequireDigit = false; options.Password.RequireLowercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequiredLength = 6; options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider; options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider; }); // ----4. 注入 UserManager、RoleManager等服务 var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), services); idBuilder.AddEntityFrameworkStores<IdDbContext>() .AddDefaultTokenProviders() .AddRoleManager<RoleManager<Role>>() .AddUserManager<UserManager<User>>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
8.执行以下命令进行数据迁移,执行后将在数据库中生成多张与 Identity 相关的表
Add-Migration InitIdentity Update-database
9.创建相应的实体类
public record LoginRequest(string UserName, string Password); public record SendResetPasswordTokenRequest(string Email); public record ResetPasswordResponse(string Email, string Token, string NewPassword);
10.在控制器中编写代码,操作角色、用户数据(重点看注释)
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Identity; namespace IdentitySample.Controllers { [ApiController] [Route("[controller]/[action]")] public class Test1Controller : ControllerBase { private readonly ILogger<Test1Controller> logger; private readonly RoleManager<Role> roleManager; private readonly UserManager<User> userManager; // 注入 RoleManager,UserManager,ILogger public Test1Controller( ILogger<Test1Controller> logger, RoleManager<Role> roleManager, UserManager<User> userManager) { this.logger = logger; this.roleManager = roleManager; this.userManager = userManager; } // 创建角色和用户 [HttpPost] public async Task<ActionResult> CreateUserRole() { // 1. 判断管理员角色是否存在,不存在则创建 bool roleExists = await roleManager.RoleExistsAsync("admin"); if (!roleExists) { Role role = new Role() { Name = "Admin" }; var r = await roleManager.CreateAsync(role); if (!r.Succeeded) { return BadRequest(r.Errors); } } // 2. 创建账户 User user = await userManager.FindByNameAsync("yzk"); if (user == null) { user = new User { UserName = "yzk", Email = "51398898@qq.com", EmailConfirmed = true, }; var r = await userManager.CreateAsync(user, "123456"); if (!r.Succeeded) { return BadRequest(r.Errors); } r = await userManager.AddToRoleAsync(user, "Admin"); if (!r.Succeeded) { return BadRequest(r.Errors); } } return Ok(); } // 登录 [HttpPost] public async Task<ActionResult> Login(LoginRequest req) { string userName = req.UserName; string password = req.Password; var user = await userManager.FindByNameAsync(userName); if (user == null) { return NotFound($"用户名不存在{userName}"); } if (await userManager.IsLockedOutAsync(user)) { return BadRequest("LockedOut"); } var success = await userManager.CheckPasswordAsync(user, password); if (success) { return Ok("Success"); } else { // 调用userManager的AccessFailedAsync方法来记录一次“登录失败”, // 当连续多次登录失败之后,账户就会被锁定一段时间,以避免账户被暴力破解 var r = await userManager.AccessFailedAsync(user); if (!r.Succeeded) { return BadRequest("AccessFailed failed"); } return BadRequest("Failed"); } } // 发送重置密码 Token [HttpPost] public async Task<IActionResult> SendResetPasswordToken( SendResetPasswordTokenRequest req) { string email = req.Email; var user = await userManager.FindByEmailAsync(email); if (user == null) { return NotFound($"邮箱不存在:[{email}]"); } // 调用GeneratePasswordResetTokenAsync方法来生成一个密码重置令牌,这个令牌会被保存到数据库中 // 然后把这个令牌发送到用户邮箱 string token = await userManager.GenerateEmailConfirmationTokenAsync(user); logger.LogInformation($"向邮箱{user.Email}发送Token={token}"); return Ok(token); } // 重置密码 [HttpPost] public async Task<IActionResult> VerifyResetPasswordToken( ResetPasswordRequest req) { string email = req.Email; var user = await userManager.FindByEmailAsync(email); string token = req.Token; string password = req.NewPassword; var r = await userManager.ResetPasswordAsync(user, token, password); return Ok(); } } }
11.启动项目,通过 Postman 或 Swagger 运行 API 进行测试