【ASP.NET CORE】 7. Identity标识框架

本系列专栏基于杨中科老师的《ASP.NET Core技术内幕与项目实战》,本人记录梳理的学习笔记,有部分的增补和省略。更全面系统的讲解,请看杨老师的视频课:【.NET教程,.Net Core视频教程,杨中科主讲】

一、标识框架

1. 概念

ASP.NET Core 标识框架 是官方提供的身份认证与授权一体化解决方案 ,核心采用基于角色的访问控制(RBAC, Role-Based Access Control) 策略,是.NET 生态中实现用户权限管理的标准方案。

框架原生能力:

  • 内置用户、角色、用户角色关联、声明(Claims)、令牌等核心数据表结构
  • 提供开箱即用的用户管理、角色管理、权限校验接口
  • 原生支持第三方外部登录(GitHub、微信、Google 等)、双因素认证(2FA)、密码重置、邮箱验证等企业级功能
  • 解耦业务与身份认证逻辑,大幅降低权限系统开发成本

2. 数据持久化特性

标识框架基于 EF Core实现数据库操作,是它最核心的优势之一:

  • 无需手动编写 SQL 语句,完全通过 EF Core 完成 CRUD
  • 支持几乎所有主流数据库:SQL Server、MySQL、PostgreSQL、SQLite、Oracle 等
  • 遵循 EF Core 的迁移机制,数据库结构可平滑升级

3. 使用流程

(1)自定义实体类

框架提供基类 IdentityUser<TKey>(用户)、IdentityRole<TKey>(角色),TKey 为主键类型(常用long/Guid/string)。推荐自定义继承类,扩展业务所需字段(如昵称、手机号、头像):

cs 复制代码
// 用户实体:继承IdentityUser<long>,主键为long类型
public class User : IdentityUser<long>
{
    // 自定义扩展字段
    public string NickName { get; set; }
    public string Avatar { get; set; }
}

// 角色实体:继承IdentityRole<long>
public class Role : IdentityRole<long>
{
    // 可扩展角色描述、权限等级等字段
    public string RoleDesc { get; set; }
}
(2)安装核心 NuGet 包

依赖包是框架运行的基础,执行安装命令:

bash 复制代码
Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore

该包整合了 Identity 核心功能与 EF Core 持久化能力。

(3)创建数据库上下文

自定义DbContext继承自 IdentityDbContext,框架会自动配置 Identity 所需的所有数据表映射:

cpp 复制代码
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

public class IdDbContext : IdentityDbContext<User, Role, long>
{
    public IdDbContext(DbContextOptions<IdDbContext> options) : base(options)
    {
    }
}

泛型参数:User(自定义用户)、Role(自定义角色)、long(主键类型)

(4)核心操作类

无需直接操作DbContext,框架提供强类型管理器,封装了所有用户 / 角色操作:

  • UserManager<TUser>:用户管理(创建、删除、修改密码、绑定角色等)
  • RoleManager<TRole>:角色管理(创建、删除、校验角色是否存在等)
  • SignInManager<TUser>:登录管理(登录、登出、双因素认证校验等)
(5)核心返回值:IdentityResult 详解

框架中创建用户、创建角色、修改密码 等方法,返回值均为 Task<IdentityResult>,用于统一封装操作结果:

cs 复制代码
// 类型定义(简化版)
public class IdentityResult
{
    // 操作是否成功
    public bool Succeeded { get; }
    // 错误信息集合(失败时返回具体原因)
    public IEnumerable<IdentityError> Errors { get; }

    // 静态方法
    public static IdentityResult Success(); // 成功
    public static IdentityResult Failed(params IdentityError[] errors); // 失败
}

// 错误详情
public class IdentityError
{
    public string Code { get; set; } // 错误码
    public string Description { get; set; } // 错误描述
}

注意:必须判断Succeeded,失败时返回Errors给前端,便于排查问题。

(6)依赖注入注册

Program.cs中注册 Identity 服务,配置密码规则、令牌提供器等:

cs 复制代码
var builder = WebApplication.CreateBuilder(args);
IServiceCollection services = builder.Services;

// 1. 注册数据库上下文
services.AddDbContext<IdDbContext>(opt =>
{
    string connStr = builder.Configuration.GetConnectionString("Default");
    opt.UseSqlServer(connStr); // 根据数据库替换为UseMySQL/UseNpgsql等
});

// 2. 注册数据保护(用于加密令牌、密码)
services.AddDataProtection();

// 3. 注册Identity核心服务(AddIdentityCore:轻量级配置)
services.AddIdentityCore<User>(options =>
{
    // 密码策略配置(根据业务调整)
    options.Password.RequireDigit = false;        // 不需要数字
    options.Password.RequireLowercase = false;    // 不需要小写字母
    options.Password.RequireNonAlphanumeric = false; // 不需要特殊字符
    options.Password.RequireUppercase = false;    // 不需要大写字母
    options.Password.RequiredLength = 6;          // 最小长度6

    // 令牌配置(密码重置、邮箱验证)
    options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
    options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
});

// 4. 完善Identity构建:绑定EF Core、角色管理器、用户管理器
var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), services);
idBuilder.AddEntityFrameworkStores<IdDbContext>() // 绑定EF Core持久化
         .AddDefaultTokenProviders() // 默认令牌生成器
         .AddRoleManager<RoleManager<Role>>() // 注册角色管理器
         .AddUserManager<UserManager<User>>(); // 注册用户管理器
(7)数据库迁移

基于 EF Core 迁移命令,自动生成 Identity 全套数据表(用户表、角色表、用户角色关联表等):

bash 复制代码
# 生成迁移文件
Add-Migration InitIdentity
# 更新到数据库
Update-Database

执行后,数据库会自动创建以下核心表:

  • AspNetUsers(用户表)
  • AspNetRoles(角色表)
  • AspNetUserRoles(用户角色关联表)
  • AspNetUserClaims(用户声明表)等
(8)实战:创建角色 + 创建用户 + 绑定角色

通过RoleManagerUserManager完成初始化数据操作:

cs 复制代码
[ApiController]
[Route("api/[controller]")]
public class AccountController : ControllerBase
{
    private readonly RoleManager<Role> _roleManager;
    private readonly UserManager<User> _userManager;

    // 依赖注入管理器
    public AccountController(RoleManager<Role> roleManager, UserManager<User> userManager)
    {
        _roleManager = roleManager;
        _userManager = userManager;
    }

    /// <summary>
    /// 初始化管理员角色和用户
    /// </summary>
    [HttpPost("InitAdmin")]
    public async Task<IActionResult> InitAdmin()
    {
        // 1. 判断角色是否存在,不存在则创建
        bool roleExists = await _roleManager.RoleExistsAsync("admin");
        if (!roleExists)
        {
            Role role = new Role { Name = "admin", RoleDesc = "系统管理员" };
            IdentityResult roleResult = await _roleManager.CreateAsync(role);
            if (!roleResult.Succeeded)
                return BadRequest(roleResult.Errors);
        }

        // 2. 判断用户是否存在,不存在则创建并绑定角色
        User user = await _userManager.FindByNameAsync("yzk");
        if (user == null)
        {
            user = new User
            {
                UserName = "yzk",
                Email = "yangzhongke8@gmail.com",
                EmailConfirmed = true, // 邮箱已验证
                NickName = "测试管理员"
            };
            // 创建用户(密码:123456)
            IdentityResult userResult = await _userManager.CreateAsync(user, "123456");
            if (!userResult.Succeeded)
                return BadRequest(userResult.Errors);

            // 为用户绑定admin角色
            IdentityResult addRoleResult = await _userManager.AddToRoleAsync(user, "admin");
            if (!addRoleResult.Succeeded)
                return BadRequest(addRoleResult.Errors);
        }

        return Ok("初始化成功");
    }
}
(9)实战:用户登录校验

完整的登录逻辑:校验用户→校验锁定状态→校验密码→重置 / 累加失败次数

cs 复制代码
/// <summary>
/// 用户登录
/// </summary>
[HttpPost("Login")]
public async Task<IActionResult> Login(LoginRequest req)
{
    string userName = req.UserName;
    string password = req.Password;

    // 1. 根据用户名查询用户
    var user = await _userManager.FindByNameAsync(userName);
    if (user == null)
        return NotFound($"用户名不存在:{userName}");

    // 2. 判断用户是否被锁定
    if (await _userManager.IsLockedOutAsync(user))
        return BadRequest("账号已锁定,请稍后重试");

    // 3. 校验密码
    bool isPasswordValid = await _userManager.CheckPasswordAsync(user, password);
    if (isPasswordValid)
    {
        // 密码正确:重置登录失败次数
        await _userManager.ResetAccessFailedCountAsync(user);
        return Ok("登录成功");
    }
    else
    {
        // 密码错误:累加失败次数(达到阈值自动锁定)
        await _userManager.AccessFailedAsync(user);
        return BadRequest("用户名或密码错误");
    }
}

// 登录请求参数
public class LoginRequest
{
    public string UserName { get; set; }
    public string Password { get; set; }
}

4. 扩展:密码重置

流程:

  1. 根据邮箱查询用户
  2. 生成一次性密码重置令牌(时效、防篡改)
  3. 发送令牌至用户(邮件 / 短信)
  4. 用户凭令牌重置密码
cs 复制代码
private readonly ILogger<AccountController> _logger;
private readonly UserManager<User> _userManager;

// 构造函数注入ILogger
public AccountController(ILogger<AccountController> logger, UserManager<User> userManager)
{
    _logger = logger;
    _userManager = userManager;
}

/// <summary>
/// 发送密码重置邮件
/// </summary>
[HttpPost("SendResetPwdEmail")]
public async Task<IActionResult> SendResetPwdEmail(string email)
{
    var user = await _userManager.FindByEmailAsync(email);
    if (user == null)
        return NotFound("邮箱未注册");

    // 生成密码重置令牌(核心方法)
    string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
    // 实际业务:将token拼接成链接,通过邮件服务发送给用户
    _logger.LogInformation($"向邮箱 {email} 发送重置令牌:{resetToken}");

    return Ok("重置邮件已发送,请查收");
}

/// <summary>
/// 重置密码
/// </summary>
[HttpPost("ResetPassword")]
public async Task<IActionResult> ResetPassword(ResetPwdRequest req)
{
    var user = await _userManager.FindByEmailAsync(req.Email);
    if (user == null)
        return NotFound();

    // 验证令牌并重置密码(令牌必须和生成时的用户匹配)
    IdentityResult result = await _userManager.ResetPasswordAsync(user, req.Token, req.NewPassword);
    if (result.Succeeded)
        return Ok("密码重置成功");

    return BadRequest(result.Errors);
}

// 重置密码请求参数
public class ResetPwdRequest
{
    public string Email { get; set; }
    public string Token { get; set; }
    public string NewPassword { get; set; }
}

二、补充知识

1. AddIdentity vs AddIdentityCore 区别

  • AddIdentity:完整注册,包含登录、认证、授权全套服务,适合 MVC/Razor Pages
  • AddIdentityCore:轻量级注册,仅核心用户 / 角色管理,适合Web API(前后端分离项目)

2. 适用场景

  • 企业后台管理系统的权限控制
  • 前后端分离项目的用户认证
  • 需要第三方登录、双因素认证的 Web 应用

3. 提升安全措施

  • 生产环境启用强密码策略
  • 密码重置令牌设置有效期
  • 登录失败次数锁定账号
  • 敏感操作开启双因素认证(2FA)
  • 邮箱 / 手机号验证后开放完整功能

总结

  1. ASP.NET Core Identity 是RBAC 权限管理的官方标准方案,基于 EF Core 支持多数据库,开箱即用。
  2. 开发核心:自定义用户 / 角色实体 → 注册服务 → 用UserManager/RoleManager操作数据。
  3. 核心返回值IdentityResult统一处理操作结果,IdentityResult.Succeeded是判断操作是否成功的关键。
  4. 框架覆盖用户注册、登录、角色管理、密码重置、2FA 等全场景,适合绝大多数.NET Web 项目。
相关推荐
xhxxx2 小时前
RAG实战-基于 Milvus 和 LangChain 实现的天龙八部阅读助手
后端·langchain·aigc
智者知已应修善业2 小时前
【输入矩阵将其按副对角线交换后输出】2024-11-27
c语言·c++·经验分享·笔记·线性代数·算法·矩阵
白衣鸽子2 小时前
Java 异常:异常类型和异常捕获原理
后端
喜欢喝果茶.2 小时前
(c#)System.Windows -> SunnyUI
c#
zjjsctcdl2 小时前
Spring之FactoryBean详解
java·后端·spring
翘着二郎腿的程序猿2 小时前
SpringBoot集成Knife4j/Swagger:接口文档自动生成,告别手写API文档
java·spring boot·后端
小鸡脚来咯2 小时前
Spring Boot 常见面试题汇总
java·spring boot·后端
小旭95272 小时前
【超详细】Spring 核心知识点全解析(IOC+AOP)
java·后端·spring·maven·intellij-idea
xx24062 小时前
RN学习笔记
笔记·学习