• 8.1标识框架


    8.1标识框架

    ASP.NET Core提供了标识框架,采用RBAC(基于角色的访问控制),内置了对用户、角色等表的管理及相关接口,框架中提供了IdentityUserIdentityRole两个实体类型,Tkey为主键类型。

    使用步骤:
    1. NuGet安装Microsoft.AspNetCore.Identity.EntityFrameworkCore
    2. 创建用户实体类和角色实体类
    //每次直接使用IdentityUser都要说明主键类型,所以直接继承
    //IdentityUser类中已经内置了用户名、密码、邮箱等属性,如果想增加自己的属性,也可以使用继承
    public class User : IdentityUser<long>
    {
    	public DateTime CreationTime { get; set; }//增加创建时间和昵称两个自定义属性
    	public string? NickName { get; set; }
    }
    
    public class Role : IdentityRole<long>
    {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    除了IdentityUser和IdentityRole,标识框架中还有IdentityRoleClaim、IdentityUserToken等实体类,这些实体类都有默认的表名,如果要修改,可以使用IEntityTypeConfiguration来对实体类进行配置

    3. 创建标识上下文类,继承自IdentityDbContext
    //泛型参数分别代表用户类型、角色类型、主键类型
    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可以通过这个类操作数据库,但是标识框架提供了RoleManagerUserManager类简化对数据库的操作,这些类封装了对IdentityDbContext的操作。

    标识框架中的方法有执行失败的可能,所以有些方法可以通过Task的返回结果来验证是否失败,IdentityResult的Succeeded属性表示是否操作成功,如果失败,则可以从Errors属性中获取错误信息,

    RoleManager常用方法:

    方法说明
    Task CreateAsync(TRole role)创建角色
    Task DeleteAsync(TRole role)删除角色
    Task RoleExistsAsync(string roleName)指定名字的角色是否存在
    Task FindByNameAsync(string roleName)根据角色名字获取角色对象

    UserManager常用方法:

    方法说明
    Task CreateAsync(TUser user,string password)创建用户
    Task UpdateAsync(TUser user)更新用户
    Task DeleteAsync(TUser user)删除用户
    Task FindByIdAsync(string userId)根据Id查找用户
    Task FindByNameAsync(string userName)根据name查找用户
    Task CheckPasswordAsync(TUser user,string password)检查用户密码是否正确,如果失败则调用AccessFailedAsync记录失败次数
    Task ChangePasswordAsync(TUser user,string currentPassword,string newPassword)修改密码
    Task GeneratePasswordResetTokenAsync(TUser user)生成令牌,用来重置密码
    Task ResetPasswordAsync(TUser user,string token,string newPassword)重置密码
    Task AddToRoleAsync(TUser user,string role)为用户增加角色
    Task RemoveFromRoleAsync(TUser user,string role)为用户删除角色
    Task> GetRolesAsync(TUser user)用户所拥有的所有角色
    Task IsInRoleAsync(TUser user,string role)判断用户是否具有某个角色
    Task IsLockedOutAsync(TUser user)判断用户是否被锁定
    Task GetLockoutEndDataAsync(TUser user)获取锁定时间
    Task SetLockoutEndDataAsync(TUser user,DataTimeOffset? lockoutEnd)设置用户锁定时间
    Task AccessFailedAsync(TUser user)记录用户登陆失败次数,多次失败应当锁定一段时间
    4. 向容器中注册服务
    IServiceCollection services = builder.Services;
    //对IdDbContext进行设置
    services.AddDbContext<IdDbContext>(opt => {
        string connStr = builder.Configuration.GetConnectionString("Default");
        opt.UseSqlServer(connStr);
    });
    services.AddDataProtection();
    //添加标识框架的一些重要基础服务,如密码几位,是否要求有大小写
    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;
    });
    var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), services);
    //注册各种服务
    idBuilder.AddEntityFrameworkStores<IdDbContext>()
        .AddDefaultTokenProviders()
        .AddRoleManager<RoleManager<Role>>()
        .AddUserManager<UserManager<User>>();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    5. 使用Add-MigrationUpdate-database生成数据库
    6. 编写控制器代码,对角色、用户进行操作
    public class Test1Controller : ControllerBase
    {
        private readonly ILogger<Test1Controller> logger;//注册日志
        private readonly RoleManager<Role> roleManager;
        private readonly UserManager<User> userManager;
        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()
    	{
    		bool roleExists = await roleManager.RoleExistsAsync("admin");//判断admin账户是否存在
    		if (!roleExists)
    		{
    			Role role = new Role { Name="Admin"};
    			var r = await roleManager.CreateAsync(role);
    			if (!r.Succeeded)//框架会存在创建失败的情况,一般都要进行判断是否成功
    			{
    				return BadRequest(r.Errors);
    			}
    		}
    		User user = await this.userManager.FindByNameAsync("yzk");//查找用户
    		if (user == null)
    		{
                //EmailConfirmed设置为true
                //使用邮箱注册时,发送验证码到邮箱,用户输入验证码后才能确认这个邮箱可用,EmailConfirmed属性表示邮箱是否确认过
                //如果邮箱确认是存在的,则可以像下面 这样直接使用
                //如果创建用户的时候不确定邮箱是否可用,则需要先调用UserManager的GenerateEmailConfirmationTokenAsync创建
                //一个字符串作为“确认令牌”,服务器将确认令牌发送到用户邮箱,用户在输入确认令牌的时候,调用UserManager的
                //ConfirmEmailAsync方法来验证令牌
    			user = new User{UserName="yzk",Email="yangzhongke8@gmail.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();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    7. 编写登陆请求的操作方法
    public record LoginRequest(string UserName,string Password);
    
    [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
    	{
            //密码错误则记录一次登陆失败,达到次数后就锁定账户一段时间,防止暴力破解
            //失败次数和锁定时间可以在AddIdentityCore中设定
            //option.Lockout.DefaultLockoutTimesSpan和option.Lockout.MaxFailedAccessAttempts来修改
    		var r = await userManager.AccessFailedAsync(user);
    		if (!r.Succeeded)
    		{
    			return BadRequest("AccessFailed failed");
    		}
    		return BadRequest("Failed");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    实现密码重置

    发送重置密码的请求

    public record SendResetPasswordTokenRequest(string Email);
    
    [HttpPost]
    public async Task<IActionResult> SendResetPasswordToken(
    			SendResetPasswordTokenRequest req)
    {
    	string email = req.Email;
    	var user = await userManager.FindByEmailAsync(email);
    	if (user == null)
    	{
    		return NotFound($"邮箱不存在{email}");
    	}
        //生成密码令牌
    	string token = await userManager.GeneratePasswordResetTokenAsync(user);
    	logger.LogInformation($"向邮箱{user.Email}发送Token={token}");
    	return Ok();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    重置密码

    public record VerifyResetPasswordRequest(string Email,string token,string newPassword);
    
    public async Task<IActionResult> VerifyResetPassword(
    			SendResetPasswordTokenRequest req)
    {
    	string email = req.Email;
    	var user = await userManager.FindByEmail(email);
    	string token = req.Token;
    	string password = req.NewPassword;
    	var r = await userManager.ResetPasswordAsync(user,token,password);//重置密码
    	return Ok();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    【记录】服务器本地tensorboard可视化events.out.tfevents.***文件
    jemter使用和优化
    Ubuntu虚拟机安装教程
    Seata分布式事务
    数字化转型模块设计
    分布式事务 —— SpringCloud Alibaba Seata
    【Research】Wafer晶圆异常检测研究
    noetic 怎么下载robotiq_modbus_tcp 从而使用robotiq二指夹爪
    可以免费文字识别app有哪些?有这3款app就能够实现提取文字自由
    数字藏品系统都有哪些功能?
  • 原文地址:https://blog.csdn.net/weixin_44064908/article/details/126558856