ASP.NET Core 中 Identity 框架的使用

Identity 框架:

1.采用基于角色的访问控制(Role-Based Access Control, 简称 RABC) 策略,内置了对用户,角色等表的管理以及相关的接口 。
2. Identity 框架使用 EF Core 对数据库进行操作。

Identity 框架使用

由于 Identity 框架是使用 EF Core 对数据库进行操作的,所以在操作上与定义 EF Core 有着类似支出

1.定义实体类:
Identity 框架已经帮助我们定义了两个实体类,IdentityUser<TKey> 和 IdentityRole<TKey>, 其中 TKey 代表主键的类型。虽然定义的实体类里面的属性已经足够我们去使用,但是如果有需要我们还是会定义继承 IdentityUser<TKey> 和 IdentityRole<TKey> 的两个实体类,在其中用来增加自定义属性。

IdentityRole 实体类:用于定义角色数据,比如不同角色,他们的操作权限也不一样
	/// <summary>
    /// 验证用户是否有权限做一些操作
    /// </summary>
    public class MyRole : IdentityRole<long>
    {
        //IdentityRole 实体中也定义了许多的列,可以在子类中添加
    }
IdentityUser 实体类:用户定义用户信息,用于验证用户登录等
	//验证用户是否登录
    public class MyUser : IdentityUser<long>
    {
        //IdentityUser 实体中已经定义了许多的列
        public string? WeiChart { get; set; }
    }

2.安装 Identity 框架所使用的 NuGet 包

Install-Package microsoft.AspNetCore.Identity.EntityFrameworkCore
Identity 框架 Nuget 包

3.定义实体配置类
和 EF Core 一样,定义实体的配置类,当然这里也可以不配置,直接叫他使用默认的配置

IdentityRole 实体配置类:
	public class MyRoleConfig : IEntityTypeConfiguration<MyRole>
    {
        public void Configure(EntityTypeBuilder<MyRole> builder)
        {
            builder.ToTable("T_MyRole");//指定表名
        }
    }
IdentityUser 实体配置类:
	public class MyUserConfig : IEntityTypeConfiguration<MyUser>
    {
        public void Configure(EntityTypeBuilder<MyUser> builder)
        {
            builder.ToTable("T_MyUser");//指定表名
        }
    }

4.创建 Identity 框架的 DbContext 类
继承 IdentityDbContext 泛型类,其中第一个泛型是 IdentityUser<TKey> 或者继承该实体的类,第二个泛型是 IdentityRole<TKey> 类型 或继承该实体的类,第三个泛型是 TKey,也就是主键类型

	//继承IdentityDbContext<MyUser, MyRole, long>
    //这里的继承类后面需要加上这三个泛型, 前两个添加是为了使用我们实体里面所自定义的属性,最后一个是主键的类型
    //你不添加的话,他就默认你的实体类是 IdentityUser, IdentityRole, 而不是继承类之后的类
    public class MyIdentityDbContext : IdentityDbContext<MyUser, MyRole, long>
    {
        //为了让我在其他程序集中调用该 DBContext, 使用 DbContextOptions 类型
        public MyIdentityDbContext(DbContextOptions dbContextOptions): base(dbContextOptions) 
        { 
        
        }

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

    }

在 EF Core 中,我们都是通过去实例化该 DbContext 类来进行对数据库的操作,但是在 Identity 框架中,给我们提供了 UserManager<T1>, 和 RoleManager<T2> 这两个泛型类,通过调用里面的方法,可以更加简便的对数据库进行操作,其中 T1 和 T2 分别是 IdentityUser<TKey> 或者继承该实体的类,第二个泛型是 IdentityRole<TKey> 类型 或继承该实体的类,比如我这里就是 MyUser 实体类和 MyRole 实体类。

5.调用 Migration 工具进行迁移
我这里由于是将 Identity 对于数据库的配置以及定义单独放在一个程序集中的,方便其他程序集调用,且可以更加灵活的使用不同的数据库,不需要在 DbContext 类中 重写 OnConfiguring(DbContextOptionsBuilder optionsBuilder) 对调用那个数据库写死,这样虽然更灵活,但是对于我们迁移就会更加的麻烦,需要在该程序集中单独定义一个类,来帮助我们迁移,操作如下

调用 Migration 工具包

Install-Package Microsoft.EntityFrameworkCore.Tools

定义继承 IDesignTimeDbContextFactory<T> 接口的类, T 即指你需要迁移的那个 DbContext,在该类中定义你对数据库的引用,使用什么数据库就调用该数据库对应的包即可
	public class IdentityDesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyIdentityDbContext>
    {
        public MyIdentityDbContext CreateDbContext(string[] args)
        {
            DbContextOptionsBuilder<MyIdentityDbContext> dbContextOptionsBuilder = new DbContextOptionsBuilder<MyIdentityDbContext>();
            //我这里使用的是 SqlServer 数据库,调用的 Microsoft.EntityFrameworkCore.SqlServer 包
            dbContextOptionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=demo1;Integrated Security=true;TrustServerCertificate=true");
            MyIdentityDbContext myDBContext = new MyIdentityDbContext(dbContextOptionsBuilder.Options);
            return myDBContext;
        }
    }

首先前提是在 DbContext 中声明参数为 DbContextOptions类型的构造器,如上述第四步所示。这样就可以在此处使用以及在其他程序集里去注册该服务,书写对数据库的配置,如下文第 6 步

//此处为第四步中 MyIdentityDbContext 类中的构造器,仅做示范
public MyIdentityDbContext(DbContextOptions dbContextOptions): base(dbContextOptions) 
{ 

}
//注册 DbContextOptions 服务,来在其他程序集中书写对数据库的配置,仅做示范,具体到第六步代码开头
builder.Services.AddDbContext<MyIdentityDbContext>(options =>
 {
     options.UseSqlServer("Data Source=.;Initial Catalog=demo1;Integrated Security=SSPI;TrustServerCertificate=true");
 });
将该程序集设为启动项,然后在程序包管理器控制台中调用 Add-Migration  名字, Update-Database 等命令即可(当然还有其他命令这里不做赘述)

在这里插入图片描述
在调用 Update-database 命令,成功即到数据库中查看对应表有没有被生成
在这里插入图片描述
6.将 Identity 所需要的服务注册进去
具体如下述代码

注意:对于服务器的配置需要在 Programs 里面配置(如下),不能在 DbContext 里面配置,不然对于 Identity 框架接下来的服务注册会报错。
builder.Services.AddDbContext(options =>
{
options.UseSqlServer(“Data Source=.;Initial Catalog=demo1;Integrated Security=SSPI;TrustServerCertificate=true”);
});

			//注册 Identity 框架-----------------------
            //首先将 写在其他程序集的 DbContext 给注册进来,书写对数据库的配置,更加灵活
            builder.Services.AddDbContext<MyIdentityDbContext>(options =>
            {
                options.UseSqlServer("Data Source=.;Initial Catalog=demo1;Integrated Security=SSPI;TrustServerCertificate=true");
            });
            //注册进行数据保护
            builder.Services.AddDataProtection();
            //builder.Services.AddIdentity(); 这种一般使用在 MVC 里面,而不是前后端分离中,它里面会提供一些界面等的操作
            //这里需要添加上对登录验证操作做一些变化,比如我这里登录用的实体是 MyUser
            builder.Services.AddIdentityCore<MyUser>(options =>
            {
                //如果使用他原本的密码的操作,会比较麻烦,他原本的密码需要你搞得创造的老复杂了,所以这里对其密码进行简化,也可以不设置
                //下面都是对密码等做的一些操作,可以去查文档
                //这里设置密码输入错误几次,写几个举例
                options.Lockout.MaxFailedAccessAttempts = 5;//输错几次锁定
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromSeconds(10);//锁定时间
                //
                options.Password.RequireDigit = false;
                options.Password.RequireLowercase = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase = false;
                options.Password.RequiredLength = 6;
                /*
                PasswordResetTokenProvider : 重置密码是发送给你的 Token,也就是 验证码
                注意:
                这里在注册时候将  options.Tokens.PasswordResetTokenProvider  设置为 TokenOptions.DefaultEmailProvider, 返回的才是数值
                options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
                设置该默认值结果:403797
                不设置该值的结果:CfDJ8LYtD6oWUe1GqPBiQxTKiPC1v0835ryQmJ8kuK86CKme6Ik6FDhmfY1uAIf01HpxshNC0/Rv4FWbwf3PNE9TYQqtYt1lX8JHwJ1ekAk6b0H6qi6gAViIMxOUFwhy4c5hMc8Fziw9r2plFzfnEwok3ps8TJaqyH+WVcNp9O4rsBvzBgg9g8dnFgC5qFi/Jvmfeg==
                不设置该值时一般是需要生成一个链接的时候使用的
                 */
                options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
                options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
            });
            //接下来和实体建立起关系
            //参数: 登录验证, 角色权限验证, 服务集合
            IdentityBuilder iBuilder = new IdentityBuilder(typeof(MyUser), typeof(MyRole), builder.Services);
            //将 实体与 DbContext 连接在一起: iBuilder.AddEntityFrameworkStores<MyIdentityDbContext>()
            //iBuilder.AddUserManager<UserManager<MyUser>>(): 添加登录验证的管理
            //iBuilder.AddRoleManager<RoleManager<MyRole>>(): 添加角色权限的管理
            //在此处添加对 UserManager 和 RoleManager 类的服务,方便我们使用
            iBuilder.AddEntityFrameworkStores<MyIdentityDbContext>().AddDefaultTokenProviders().AddUserManager<UserManager<MyUser>>().AddRoleManager<RoleManager<MyRole>>();
            //看上面哪一行代码,之前我们知道,可以直接实例化 DbContext 来调用里面的 实体来完成操作,但是这里可以不这么做
            //为什么后面调用 .AddUserManager<UserManager<MyUser>>().AddRoleManager<RoleManager<MyRole>>();,就是因为我们可以使用 Identity 框架给我们提供的 UserManager 和 UserManager 类来操作对应的实体
            //----------------------------------------

7.使用 UserManager 和 RoleManager 来操纵表数据
下面为示例,具体方法使用的时候去查一下文档或者到该类中去看即可,英文意思即字面意思

[Route("api/[controller]/[action]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        //将需要的类注入进来,这里就不需要使用 DbContext 
        private readonly UserManager<MyUser> userManager;
        private readonly RoleManager<MyRole> roleManager;
        private readonly IHostEnvironment hostEn;
        public ValuesController(RoleManager<MyRole> roleManager, UserManager<MyUser> userManager, IHostEnvironment hostEn)
        {
            this.userManager = userManager;
            this.roleManager = roleManager;
            this.hostEn = hostEn;
        }
        [HttpPost]
        public async Task<ActionResult<ObjectResult>> Test()
        {
            //查看有没有 admin 角色,如果没有,就给其加入
            bool isHas = await this.roleManager.RoleExistsAsync("admin");
            if (!isHas)
            {
                MyRole myRole = new MyRole();
                myRole.Name = "admin";
                myRole.NormalizedName = "admin";
                myRole.ConcurrencyStamp = "1";

                //添加进去,创建改行并添加进去
                IdentityResult result =  await this.roleManager.CreateAsync(myRole);//创建角色
                //如果没有成功返回个报错
                if (!result.Succeeded)
                {
                    //BadResult 是 Controller 父类中的方法,其返回值继承于 ObjectResult
                    //按道理不应该将报错信息反馈上去,应该用日志记录,报错反馈个字符串即可
                    return BadRequest(result.Errors);
                }
            }
            //查看有没有 pengmingxing 的用户,如果有,就返回,没有就创建,并且其角色为 admin
            MyUser user = await this.userManager.FindByNameAsync("pengmingxing");//根据用户名查找对应用户
            if (user == null)
            {
                MyUser myUser = new MyUser();
                myUser.UserName = "pengmingxing";
                myUser.Email = "2785218977@qq.com";
                myUser.EmailConfirmed = true;//输入密码需要邮箱验证
                myUser.PhoneNumber = "88888888888";
                myUser.WeiChart = "88888888";
                
                //这里使用的 CreateAsync 创建用户这个方法可以不设置密码,比如手机验证码登录
                //IdentityResult result = await  this.userManager.CreateAsync(myUser);//创建用户
                //如果相设置密码,可以使用另外一个 重写
                IdentityResult result = await this.userManager.CreateAsync(myUser, "888888");//创建用户
                if (!result.Succeeded)
                {
                    return BadRequest(result.Errors);
                }

                //将该用户和 admin 角色绑定在一起
                result = await this.userManager.AddToRoleAsync(myUser, "admin");//把用户添加到角色
                if (!result.Succeeded)
                {
                    return BadRequest(result.Errors);
                }
            }
            else
            {
                //判断用户是不是某个角色,没有则创建
                bool isRole = await userManager.IsInRoleAsync(user, "admin");
                if (!isRole)
                {
                    //将该用户和 admin 角色绑定在一起
                    IdentityResult result = await this.userManager.AddToRoleAsync(user, "admin");//把用户添加到角色
                    if (!result.Succeeded)
                    {
                        return BadRequest(result.Errors);
                    }
                }
            }
            return Ok();
        }

        [HttpPost]
        public async Task<ActionResult<string>> CheckUserPassword(string userName, string password)
        {
            //查看一下有没有对应用户
            MyUser user = await userManager.FindByNameAsync(userName);
            if (user == null)
            {
                //有些人会恶意的去黑网站,所以这里在正式环境里最好没有用户名不要给特别明确的信息
                if (hostEn.IsDevelopment())
                {
                    return BadRequest("用户名不存在");
                }
                else
                {
                    return BadRequest();
                }
            }

            //先判断下该用户有没有被封,被锁住
            bool isLock = await userManager.IsLockedOutAsync(user);
            if (isLock)
            {
                return BadRequest($"该用户已被锁住,锁住结束时间{user.LockoutEnd}");
            }
           
            //用户名存在就确定下密码对不对
            bool isTrue = await userManager.CheckPasswordAsync(user, password);
            if (isTrue)
            {
                //如果登录成功,将记录的登录失败数据删除
                await userManager.ResetAccessFailedCountAsync(user);
                return Ok("登录成功");
            }
            else
            {
                //记录一下输入错误数据
                await userManager.AccessFailedAsync(user);
                //获取登录失败次数
                int failNum = await userManager.GetAccessFailedCountAsync(user);
                //userManager.GetAccessFailedCountAsync(user);该方法记录错误次数,超过固定次数后会给他锁定该用户
                //可以在前面定义的时候就将其设置,也可以使用期默认值,也可以灵活的使用里面的方法,来设置锁定时间

                return BadRequest("用户名或密码错误错误");
            }
        }

        [HttpPost]
        public async Task<ActionResult<string>> SendPasswordToken(string userName)
        {
            //寻找下该用户
            MyUser user = await userManager.FindByNameAsync(userName);
            if (user == null)
            {
                if (hostEn.IsDevelopment())
                {
                    return BadRequest("用户名不存在");
                }
                return BadRequest();
            }

            //获取到 Token
            string token = await userManager.GeneratePasswordResetTokenAsync(user);
            //这里就不将 Token 发送了,直接写在 控制台上
            /*
            注意:
            这里在注册时候将  options.Tokens.PasswordResetTokenProvider  设置为 TokenOptions.DefaultEmailProvider, 返回的才是数值
            options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
            设置该默认值结果:403797
            不设置该值的结果:CfDJ8LYtD6oWUe1GqPBiQxTKiPC1v0835ryQmJ8kuK86CKme6Ik6FDhmfY1uAIf01HpxshNC0/Rv4FWbwf3PNE9TYQqtYt1lX8JHwJ1ekAk6b0H6qi6gAViIMxOUFwhy4c5hMc8Fziw9r2plFzfnEwok3ps8TJaqyH+WVcNp9O4rsBvzBgg9g8dnFgC5qFi/Jvmfeg==
            下面第二种情况一般是需要生成一个连接的时候使用的
             */
            Console.WriteLine(token);

            return Ok();
        }

        /// <summary>
        /// 获取到生成的 Token,然后修改密码
        /// 这个 Token 不知道保存在那个表里的,有可能保存在对应的表数据的字段里,然后给他加密了,防止有些人窃取
        /// 或者其内部有个空间,专门给他保存下来了
        /// </summary>
        /// <param name="myUser"></param>
        /// <param name="token"></param>
        /// <param name="newPassword"></param>
        /// <returns></returns>
        [HttpPut]
        public async Task<ActionResult<string>> ResetPasswordByToken(string myUser, string token, string newPassword)
        {
            MyUser user = await userManager.FindByNameAsync(myUser);
            if (user == null)
            {
                if (hostEn.IsDevelopment())
                {
                    return BadRequest("用户名不存在");
                }
                return BadRequest();
            }

            //重置用户名
            IdentityResult result = await userManager.ResetPasswordAsync(user, token, newPassword);
            if (result.Succeeded)
            {
                //清空之前的输错密码记录
                await userManager.ResetAccessFailedCountAsync(user);
                return Ok("密码修改成功");
            }
            else
            {
                //失败就记录下来
                await userManager.AccessFailedAsync(user);
                return BadRequest("密码修改失败");
            }
        }
    }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值