8.1标识框架

举报
步步为营 发表于 2023/02/20 19:49:10 2023/02/20
【摘要】 ASP.NET Core提供了标识框架,采用RBAC(基于角色的访问控制),内置了对用户、角色等表的管理及相关接口,框架中提供了`IdentityUser<TKey>`和`IdentityRole<TTKey>`两个实体类型,Tkey为主键类型。

8.1标识框架

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

使用步骤:

1. NuGet安装Microsoft.AspNetCore.Identity.EntityFrameworkCore

2. 创建用户实体类和角色实体类

//每次直接使用IdentityUser<long>都要说明主键类型,所以直接继承
//IdentityUser类中已经内置了用户名、密码、邮箱等属性,如果想增加自己的属性,也可以使用继承
public class User : IdentityUser<long>
{
	public DateTime CreationTime { get; set; }//增加创建时间和昵称两个自定义属性
	public string? NickName { get; set; }
}

public class Role : IdentityRole<long>
{
}

除了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);
	}
}

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

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

RoleManager常用方法:

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

UserManager常用方法:

方法 说明
Task<IdentityResult> CreateAsync(TUser user,string password) 创建用户
Task<IdentityResult> UpdateAsync(TUser user) 更新用户
Task<IdentityResult> DeleteAsync(TUser user) 删除用户
Task<IUser> FindByIdAsync(string userId) 根据Id查找用户
Task<IUser> FindByNameAsync(string userName) 根据name查找用户
Task<Bool> CheckPasswordAsync(TUser user,string password) 检查用户密码是否正确,如果失败则调用AccessFailedAsync记录失败次数
Task<IdentityResult> ChangePasswordAsync(TUser user,string currentPassword,string newPassword) 修改密码
Task<string> GeneratePasswordResetTokenAsync(TUser user) 生成令牌,用来重置密码
Task<IdentityResult> ResetPasswordAsync(TUser user,string token,string newPassword) 重置密码
Task<IdentityResult> AddToRoleAsync(TUser user,string role) 为用户增加角色
Task<IdentityResult> RemoveFromRoleAsync(TUser user,string role) 为用户删除角色
Task<IList<string>> GetRolesAsync(TUser user) 用户所拥有的所有角色
Task<bool> IsInRoleAsync(TUser user,string role) 判断用户是否具有某个角色
Task<bool> IsLockedOutAsync(TUser user) 判断用户是否被锁定
Task<DataTimeOffset?> GetLockoutEndDataAsync(TUser user) 获取锁定时间
Task<DataTimeOffset> SetLockoutEndDataAsync(TUser user,DataTimeOffset? lockoutEnd) 设置用户锁定时间
Task<IdentityResult> 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>>();

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();
	}
}

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");
	}
}

实现密码重置

发送重置密码的请求

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();
}

重置密码

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();
}

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。