在ASP.NET Core WebApi中使用标识框架(Identity)

发布于:2025-06-29 ⋅ 阅读:(19) ⋅ 点赞:(0)

一.什么是标识框架

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-MigrationUpdate-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 createIdentityUpdate-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站视频学习.


网站公告

今日签到

点亮在社区的每一天
去签到