【ASP.NET Core】身份认证——Identity标识框架指南

该文章已生成可运行项目,


前言

在ASP.NET Core 中,有两道非常重要的安全访问机制,分别是认证(Authentication)和授权(Authorization)。本篇文章主要介绍在ASP.NET Core中通过官方的Identity身份认证框架来实现访问用户的认证,授权的部分后面会通过另一篇关于JWT的实现来介绍使用。

* 身份认证与授权

在ASP.NET Core 中,身份认证(Authentication)和授权(Authorization)是我们绕不开的用于保障应用的两道安全机制。无论是我们传统开发中基于session或者是现代开发中流行的JWT,本质上还是通过先认证,后授权的方式。

身份认证(Authentication):比方说在登录流程中或使用客户端验证信息验证身份,这都属于身份认证(Authentication)的范畴:

  • 首次登录:用户提交用户名和密码,服务器端验证登录凭据
    • 使用session的情况:服务器生成一个唯一的SessionID,并将用户信息(如身份、权限等)存储在服务器端,将SessionID返回给客户端保存;
    • 使用JWT的情况:服务器生成包含用户身份信息的 JWT 令牌返回给客户端。
  • 完成登录后请求:客户端携带保存的cookie或者JWT
    • 使用session的情况:客户端将唯一SessionID通过cookie传给服务器用于验证身份;
    • 使用JWT的情况:客户端将JWT放在header发送到服务器,服务器验证是否被串改验证身份,解析payload。

授权(Authorization):而在身份已确认的基础上,判断该用户是否有权限访问请求的资源。这些都是属于授权(Authorization)的范畴

  • 访问后台管理员页面:通过上一步身份认证来验证当前用户后,鉴别此用户是否具有管理员权限访问。

换句话说,Authentication就是指系统用于验证当前访问的用户身份,通过用户名密码、cookie、令牌等确认是否是合法用户,也就是解析当前用户是谁。而Authorization是用来确定这个用户是否有权限去访问请求的资源,也就是判断用户能做什么。


一、ASP.NET Core中的Identity

ASP.NET Core Identity是一个管理用户、密码、配置文件数据、角色、声明、令牌、电子邮件确认等的身份验证系统,是微软官方提供的身份认证框架。

比起我们日常工作里自己去设计用户表、密码加密,ASP.NET Core Identity提供了一套契合实际工作生产的身份认证机制,比如封装了登录、注册、重置等常用方法。在一些安全性上也包含了密码哈希加密,账号锁定等策略。并且我们也可以自定义属性用于扩展登录上的一些操作。

  • 标识(ldentity)框架:采用基于角色的访问控制(Role-Based Access Control,简称RBAC)策略,内置了对用户、角色等表的管理以及相关的接口,支持外部登录、2FA等,
  • 标识框架使用EF Core对数据库进行操作,因此标识框架支持几乎所有数据库。
    ASP.NET Core Identity框架提供了一组开箱即用的api,帮助我们去管理身份验证。这里通过webapi的方式搭建一个包含登录和注册和重置密码的操作,下面让我们开始了解并且使用这个官方的身份认证框架。

二、Identity框架架构

2.1 Identity上下文

Identity框架本质上是还是一个基于EF Core的框架,通过两个核心的上下文(IdentityDbContext ,IdentityUserContext )对用户、角色、权限等核心数据结构进行操作。属于数据访问的底层支撑,负责将实体模型映射到数据库,并提供基础的数据操作能力。

其中IdentityUserContext继承自DbContext,IdentityDbContext继承自IdentityUserContext。这两个上下文的区别在于:

  • IdentityUserContext: 作为一个基础上下文,处理用户及用户关联数据,比如用户TUser、用户声明TUserClaim、用户登录TUserLogin、用户令牌TUserToken。简而言之,它适用于仅需用户认证的简单系统,IdentityUserContext只包含对用户相关数据操作。
  • IdentityDbContext:是一个完整的Identity上下文,它继承了IdentityUserContext,并且包含了角色及角色关联的数据。比如角色,角色声明、用户和角色关联关系。它使用于基于角色的访问控制(Role-Based Access Control,简称RBAC)策略的复杂系统。

2.1.1 IdentityUserContext

2.1.1.1 IdentityUserContext泛型类

IdentityUserContext的核心是一个名为IdentityUserContext的抽象泛型类,它接收User相关的类作为构造函数。也包含各种默认的构造方法。

IdentityUserContext< TUser>和 public class IdentityUserContext< TUser, TKey>最后都是通过内部的base关键字,调用抽象类IdentityUserContext< TUser, TKey, TUserClaim, TUserLogin, TUserToken>。

抽象类IdentityUserContext< TUser, TKey, TUserClaim, TUserLogin, TUserToken>继承自DbContext,通过内部base关键字调用DbContext构造函数。

这些泛型类的不同版本,都默认有一个空构造函数。主要是为了兼容EFCore的DbContext的空构造函数,实现约定大于配置。并且在通过工具迁移的时候能够至少保证有无参构造函数供调用。

IdentityUserContext源码

	
	public class IdentityUserContext<TUser> : IdentityUserContext<TUser, string> where TUser : IdentityUser
	{
	    /// <summary>
	    /// Initializes a new instance of <see cref="IdentityUserContext{TUser}"/>.
	    /// </summary>
	    /// <param name="options">The options to be used by a <see cref="DbContext"/>.</param>
	    public IdentityUserContext(DbContextOptions options) : base(options) { }
	
	    /// <summary>
	    /// Initializes a new instance of the <see cref="IdentityUserContext{TUser}" /> class.
	    /// </summary>
	    protected IdentityUserContext() { }
	}
	
	/// <summary>
	/// Base class for the Entity Framework database context used for identity.
	/// </summary>
	/// <typeparam name="TUser">The type of user objects.</typeparam>
	/// <typeparam name="TKey">The type of the primary key for users and roles.</typeparam>
	public class IdentityUserContext<TUser, TKey> : IdentityUserContext<TUser, TKey, IdentityUserClaim<TKey>, IdentityUserLogin<TKey>, IdentityUserToken<TKey>>
	    where TUser : IdentityUser<TKey>
	    where TKey : IEquatable<TKey>
	{
	    /// <summary>
	    /// Initializes a new instance of the db context.
	    /// </summary>
	    /// <param name="options">The options to be used by a <see cref="DbContext"/>.</param>
	    public IdentityUserContext(DbContextOptions options) : base(options) { }
	
	    /// <summary>
	    /// Initializes a new instance of the class.
	    /// </summary>
	    protected IdentityUserContext() { }
	}
	
	/// <summary>
	/// Base class for the Entity Framework database context used for identity.
	/// </summary>
	/// <typeparam name="TUser">The type of user objects.</typeparam>
	/// <typeparam name="TKey">The type of the primary key for users and roles.</typeparam>
	/// <typeparam name="TUserClaim">The type of the user claim object.</typeparam>
	/// <typeparam name="TUserLogin">The type of the user login object.</typeparam>
/// <typeparam name="TUserToken">The type of the user token object.</typeparam>
public abstract class IdentityUserContext<TUser, TKey, TUserClaim, TUserLogin, TUserToken> : DbContext
    where TUser : IdentityUser<TKey>
    where TKey : IEquatable<TKey>
    where TUserClaim : IdentityUserClaim<TKey>
    where TUserLogin : IdentityUserLogin<TKey>
    where TUserToken : IdentityUserToken<TKey>
{
    /// <summary>
    /// Initializes a new instance of the class.
    /// </summary>
    /// <param name="options">The options to be used by a <see cref="DbContext"/>.</param>
    public IdentityUserContext(DbContextOptions options) : base(options) { }

    /// <summary>
    /// Initializes a new instance of the class.
    /// </summary>
    protected IdentityUserContext() { }
}
2.1.1.2 核心属性

IdentityUserContext包含四个核心属性,对应 Identity 框架中存储用户相关数据的数据库表。主要用于管理用户身份信息。

属性被vritual修饰,允许子类重写,实现自定义逻辑。比如添加一些操作审计日志之类的。

IdentityUserContext方法

 /// <summary>
 /// Gets or sets the <see cref="DbSet{TEntity}"/> of Users.
 /// </summary>
 public virtual DbSet<TUser> Users { get; set; } = default!;

 /// <summary>
 /// Gets or sets the <see cref="DbSet{TEntity}"/> of User claims.
 /// </summary>
 public virtual DbSet<TUserClaim> UserClaims { get; set; } = default!;

 /// <summary>
 /// Gets or sets the <see cref="DbSet{TEntity}"/> of User logins.
 /// </summary>
 public virtual DbSet<TUserLogin> UserLogins { get; set; } = default!;

 /// <summary>
 /// Gets or sets the <see cref="DbSet{TEntity}"/> of User tokens.
 /// </summary>
 public virtual DbSet<TUserToken> UserTokens { get; set; } = default!;

2.1.1.3 模型创建

IdentityUserContext通过一个OnModelCreating重载,实现了TUser,TUserClaim,TUserLogin,TUserToken模型的配置。具体的初始化方法在OnModelCreating调用的OnModelCreatingVersion里。值得注意的是OnModelCreatingVersion1和OnModelCreatingVersion2都是虚方法,方便子类重写。

protected override void OnModelCreating(ModelBuilder builder)
{
    var version = GetStoreOptions()?.SchemaVersion ?? IdentitySchemaVersions.Version1;
    OnModelCreatingVersion(builder, version);
}


internal virtual void OnModelCreatingVersion(ModelBuilder builder, Version schemaVersion)
{
    if (schemaVersion >= IdentitySchemaVersions.Version2)
    {
        OnModelCreatingVersion2(builder);
    }
    else
    {
        OnModelCreatingVersion1(builder);
    }
}

internal virtual void OnModelCreatingVersion2(ModelBuilder builder)
{
}

internal virtual void OnModelCreatingVersion1(ModelBuilder builder)
{

}

2.1.2 IdentityDbContext

2.1.2.1 IdentityDbContext泛型类

和IdentityUserContext类似,IdentityDbContext的核心也是一个名为IdentityDbContext的抽象泛型类,它接收User和Role相关的类(TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken)作为构造函数。也包含各种默认的构造方法。

IdentityDbContext, IdentityDbContext< TUser>和 IdentityDbContext< TUser, TRole, TKey>最后都是通过内部的base关键字,调用抽象类IdentityDbContext< TUser, TKey, TUserClaim, TUserLogin, TUserToken>。

IdentityDbContext< TUser, TKey, TUserClaim, TUserLogin, TUserToken>继承自IdentityUserContext,使用IdentityUserContext<TUser, TKey, TUserClaim, TUserLogin, TUserToken>的构造函数。在内部通过base关键字调用IdentityUserContext的构造函数。

IdentityUserContext源码

public class IdentityDbContext : IdentityDbContext<IdentityUser, IdentityRole, string>
{
    public IdentityDbContext(DbContextOptions options) : base(options) { }
    protected IdentityDbContext() { }
}

public class IdentityDbContext<TUser> : IdentityDbContext<TUser, IdentityRole, string> where TUser : IdentityUser
{
    public IdentityDbContext(DbContextOptions options) : base(options) { }

    protected IdentityDbContext() { }
}

public class IdentityDbContext<TUser, TRole, TKey> : IdentityDbContext<TUser, TRole, TKey, IdentityUserClaim<TKey>, IdentityUserRole<TKey>, IdentityUserLogin<TKey>, IdentityRoleClaim<TKey>, IdentityUserToken<TKey>>
    where TUser : IdentityUser<TKey>
    where TRole : IdentityRole<TKey>
    where TKey : IEquatable<TKey>
{
    public IdentityDbContext(DbContextOptions options) : base(options) { }

    protected IdentityDbContext() { }
}

public abstract class IdentityDbContext<TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken> : IdentityUserContext<TUser, TKey, TUserClaim, TUserLogin, TUserToken>
    where TUser : IdentityUser<TKey>
    where TRole : IdentityRole<TKey>
    where TKey : IEquatable<TKey>
    where TUserClaim : IdentityUserClaim<TKey>
    where TUserRole : IdentityUserRole<TKey>
    where TUserLogin : IdentityUserLogin<TKey>
    where TRoleClaim : IdentityRoleClaim<TKey>
    where TUserToken : IdentityUserToken<TKey>
{
    public IdentityDbContext(DbContextOptions options) : base(options) { }

    protected IdentityDbContext() { }
}
2.1.2.2 核心属性

比起IdentityUserContext已经包含的用户属性,IdentityDbContext内部有三个角色相关的属性——UserRoles,Roles和RoleClaims。对应 Identity 框架中存储角色相关数据的数据库表。主要用于管理角色,角色声明和用户角色绑定信息。

属性同样被vritual修饰,允许子类重写,实现自定义逻辑。

IdentityDbContext方法

 /// <summary>
/// Gets or sets the <see cref="DbSet{TEntity}"/> of User roles.
/// </summary>
public virtual DbSet<TUserRole> UserRoles { get; set; } = default!;

/// <summary>
/// Gets or sets the <see cref="DbSet{TEntity}"/> of roles.
/// </summary>
public virtual DbSet<TRole> Roles { get; set; } = default!;

/// <summary>
/// Gets or sets the <see cref="DbSet{TEntity}"/> of role claims.
/// </summary>
public virtual DbSet<TRoleClaim> RoleClaims { get; set; } = default!;
2.1.2.3 模型创建

IdentityUserContext通过一个OnModelCreating重载,实现了TUser(指定外键关系),TRole,TRoleClaim,TUserRole模型的配置。(具体方法在OnModelCreating调用的OnModelCreatingVersion里)


protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
}

前面我们提到,IdentityUserContext里的OnModelCreatingVersion虚方法。这里IdentityDbContext继承自IdentityUserContext,重写了OnModelCreatingVersion虚方法。通过base关键字调用父类的OnModelCreatingVersion2方法,初始化User相关的配置,然后在下面继续编写Role相关的配置。

internal override void OnModelCreatingVersion2(ModelBuilder builder)
{
    base.OnModelCreatingVersion2(builder);

    // Current no differences between Version 2 and Version 1
    builder.Entity<TUser>(b =>
    {
        b.HasMany<TUserRole>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
    });

    builder.Entity<TRole>(b =>
    {
       /.../ 
    });

    builder.Entity<TRoleClaim>(b =>
    {
       /.../ 
    });

    builder.Entity<TUserRole>(b =>
    {
      /.../ 
    });
}

2.2 Identity Manager

Identity框架中,Managers可以理解为一个业务逻辑的封装,封装身份认证的核心逻辑。Manager 组件有UserManager、SignInManager,RoleManager。

  • UserManager< TUser>:提供用于在持久性存储中管理用户的 API,负责用户的创建、查询、更新、删除、密码管理、角色分配等操作
    • 创建用户 CreateAsync()
    • 删除用户 DeleteAsync()
    • 验证密码 CheckPasswordAsync()
    • 修改密码 ChangePasswordAsync()
    • 分配角色 AddToRoleAsync()
    • 移除角色 RemoveFromRoleAsync()
    • 添加用户声明 AddClaimAsync()
    • 获取用户声明 GetClaimsAsync()
    • 锁定用户 SetLockoutEndDateAsync() 【直到指定的结束日期过去。 设置过去结束日期会立即解锁用户 】
    • 完整API: 微软官方文档
  • SignInManager< TUser>:提供用于用户登录的 API,负责用户登录、注销、身份验证等会话管理操作
    • 密码登录 PasswordSignInAsync()
    • 外部登录用户 ExternalLoginSignInAsync()
    • 注销 SignOutAsync()
    • 验证用户是否已登录 IsSignedIn()
    • 双因素认证 TwoFactorRecoveryCodeSignInAsync()
    • 完整API: 微软官方文档
  • RoleManager< TRole>:提供用于管理持久性存储区中角色的 API,用于角色的创建、查询、更新、删除等管理操作
    • 创建角色 CreateAsync()
    • 删除角色 DeleteAsync()
    • 检查角色是否存在 RoleExistsAsync()
    • 为角色添加声明 AddClaimAsync()
    • 为角色移除声明 RemoveClaimAsync()
    • 完整API: 微软官方文档

这类Manager不直接与上文提到的Identity上下文的直接依赖,而是通过IUserStore或者IRoleStore间接访问上下文。UserManager通过IUserStore访问IdentityUserContext上下文,RoleManager通过IRoleStore访问IdentityDbContext上下文,SignInManager依赖UserManager。

这样一来,我们就能在Controller里,通过依赖注入各类identity Manager,调用identity实体数据。

3.5小结有关于UserManager,RoleManager的使用示例

三、Identity的使用

3.1 Nuget引入

dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --version 9.0.8

3.2 创建用户类和角色类

ldentity框架采用的基于角色的访问控制策略,我们需要建立两个基础类来作为实际数据的映射,分别是用户类和角色类。它们需要各种继承自一个IdentityUser和IdentityRole的泛型

IdentityUser和IdentityRole的泛型类型必须一致,也就是User类和Role类的主键类型必须一致,不然后续对IdentityDbContext的操作会无法编译通过

public class User: IdentityUser<long>
{
    public string? NickName { get; set; }

    public long? RowVersion { get; set; }
}

public class Role : IdentityRole<long>
{
}

3.3 dbContext的引用

这里我们引用完整的Identity上下文——IdentityDbContext。
IdentityDbContext支持传入自定义User和Role写法的构造方法,第三个参数是全部Identity相关实体的主键。

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

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        //base.OnConfiguring(optionsBuilder);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
    }
}

3.4 Program.cs注册服务

builder.Services里注册Identity相关的配置。

// 配置数据保护服务,用于加密和解密数据
builder.Services.AddDataProtection();
// 配置Identity身份验证服务,设置用户和角色管理选项
builder.Services.AddIdentity<User,Role>(options =>
{
    // 设置密码策略选项
    options.Password.RequireDigit = false;           // 密码不要求包含数字
    options.Password.RequiredLength = 6;             // 密码最小长度为6位
    options.Password.RequireLowercase = false;       // 密码不要求包含小写字母
    options.Password.RequireNonAlphanumeric = false; // 密码不要求包含非字母数字字符
    options.Password.RequireUppercase = false;       // 密码不要求包含大写字母
    
    // 设置令牌提供程序选项
    options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;      // 密码重置令牌使用默认邮件提供程序
    options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;  // 邮箱确认令牌使用默认邮件提供程序
})
// 配置EntityFramework存储提供程序,使用CoreDbContext作为数据上下文
.AddEntityFrameworkStores<CoreDbContext>()
// 添加默认令牌提供程序用于生成安全令牌
.AddDefaultTokenProviders();

3.5 Controller通过identity manager调用identity实体数据

完整的登录,注册,重置和登出方法。
邮箱服务可以参考我的另一篇博客
链接: 【ASP.NET Core】基于MailKit(SMTP 协议)实现邮件发送

    [Route("api/[controller]/[action]")]
    [ApiController]
    public class AuthController : ControllerBase
    {
        private readonly ILogger<AuthController> _logger;
        private readonly IWebHostEnvironment _webHostEnvironment;
        private readonly IJWTService _jwtService;
        private readonly UserManager<User> _userManager;
        private readonly RoleManager<Role> _roleManager;
        private readonly SignInManager<User> _signInManager;
        private readonly EmailService _emailService;

        public AuthController(ILogger<AuthController> logger, IJWTService jwtService, UserManager<User> userManager, RoleManager<Role> roleManager, IWebHostEnvironment webHostEnvironment = null, EmailService emailService = null)
        {
            _logger = logger;
            _jwtService = jwtService;
            _userManager = userManager;
            _roleManager = roleManager;
            _webHostEnvironment = webHostEnvironment;
            _emailService = emailService;
        }


        /// <summary>
        /// 登录
        /// </summary>
        /// <param name="loginUser"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult<LoginResponse>> Login([FromBody] LoginUserReq loginUser)
        {
            if (loginUser.UserName == null && loginUser.Email == null)
            {
                return Ok("请输入用户名或邮箱");
            }
            User? user = await _userManager.FindByNameAsync(loginUser.UserName);
            if (user == null)
            {
                user = await _userManager.FindByEmailAsync(loginUser.Email);
                if (user == null)
                {
                    if (_webHostEnvironment.IsDevelopment())
                    {
                        return Ok("用户不存在");
                    }
                    else
                    {
                        return Ok("账号或密码错误");
                    }
                }
            }
            if (await _userManager.IsLockedOutAsync(user))
            {
                return Ok($"用户被锁,{user.LockoutEnd}");
            }
            if (!await _userManager.CheckPasswordAsync(user, loginUser.Password))
            {
                if (_webHostEnvironment.IsDevelopment())
                {
                    return Ok("登录密码错误");
                }
                else
                {
                    return BadRequest("账号或密码错误");
                }
            }
            HttpUser httpUser = new HttpUser()
            {
                UserId = user.Id,
                Name = user.UserName,
                NickName = user.NickName,
                RoleList = (await _userManager.GetRolesAsync(user)).ToList(),
            };
            var token = _jwtService.GenerateToken(httpUser);
            return new LoginResponse()
            {
                Token = token
            };
        }

        /// <summary>
        /// 发送重置密码验证码
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        [HttpGet]
        public async Task<ActionResult> SendRestPasswordCode(string userName)
        {
            User? user = await _userManager.FindByNameAsync(userName);
            if (user == null)
            {
                if (_webHostEnvironment.IsDevelopment())
                {
                    return Ok("用户不存在");
                }
                return Ok("执行有误");
            }
            if (user.Email == null || user.Email == "")
            {
                return Ok("用户没有邮箱");
            }
            var token = await _userManager.GeneratePasswordResetTokenAsync(user);
            try
            {
                await _emailService.SendEmailAsync(
                    user.Email,
                    "重置密码",
                    $"<h1>验证码如下</h1><p>{token}</p>");

                return Content("邮件发送成功");
            }
            catch (Exception ex)
            {
                return Content($"邮件发送失败: {ex.Message}");
            }
        }

        /// <summary>
        /// 重置密码
        /// </summary>
        /// <param name="resetPasswordReq"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult> ResetPassword(ResetPasswordReq resetPasswordReq)
        {
            var user = await _userManager.FindByNameAsync(resetPasswordReq.UserName);
            if (user == null)
            {
                if (_webHostEnvironment.IsDevelopment())
                {
                    return Ok("用户不存在");
                }
                return Ok("执行有误");
            }
            var result = await _userManager.ResetPasswordAsync(user, resetPasswordReq.Token, resetPasswordReq.Password);
            if (result.Succeeded)
            {
                return Ok("密码重置成功");
            }
            return Ok("密码重置失败");
        }

        /// <summary>
        /// 注册
        /// </summary>
        /// <param name="registerUser"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<ActionResult> Register([FromBody] RegisterUserReq registerUser)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            var user = new User()
            {
                UserName = registerUser.UserName,
                Email = registerUser.Email,
                EmailConfirmed = false, // 初始设置为未验证
            };
            var result = await _userManager.CreateAsync(user, registerUser.Password);
            if (result.Succeeded)
            {

                //生产邮箱验证码
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                try
                {
                    await _emailService.SendEmailAsync(
                        user.Email,
                        "账号注册",
                        $"<h1>验证链接如下</h1><p>https://localhost:7154/api/auth/ConfirmRegister/{user.Id}/{code}</p>");

                    return Content("邮件发送成功");
                }
                catch (Exception ex)
                {
                    return Content($"邮件发送失败: {ex.Message}");
                }
            }
            foreach (var error in result.Errors)
            {
                ModelState.AddModelError(string.Empty, error.Description);
            }

            return BadRequest(ModelState);
        }

        /// <summary>
        /// 确认注册
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="code"></param>
        /// <returns></returns>
        [HttpGet("{userId}/{code}")]
        public async Task<ActionResult> ConfirmRegister(string userId, string code)
        {
            if (userId == null || code == null)
            {
                return BadRequest("请输入合适的参数");
            }

            var user = await _userManager.FindByIdAsync(userId);
            if (user == null)
            {
                return NotFound($"用户名无效。");
            }

            // 解码令牌
            var decodedCode = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));

            // 确认邮箱
            var result = await _userManager.ConfirmEmailAsync(user, decodedCode);
            if (result.Succeeded)
            {
                var created = await _userManager.AddToRoleAsync(user, "admin");
                if (!created.Succeeded)
                {
                    return Ok("用户添加角色失败");
                }
                return Ok("验证成功");
            }
            else
            {
                return BadRequest("验证失败");
            }
        }

        /// <summary>
        /// 登出
        /// </summary>
        /// <returns></returns>
        public async Task<ActionResult> LoginOut(string userId)
        {
            await _signInManager.SignOutAsync();
            return Ok("登出成功");
        }
    }

总结

以上就是如何使用Identity 框架快速实现用户管理功能,当然Identity标识框架的功能不止于此,后续我会大家总结各种特殊的用法。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值