ASP.NET Core提供了标识框架,采用RBAC(基于角色的访问控制),内置了对用户、角色等表的管理及相关接口,框架中提供了IdentityUser
和IdentityRole
两个实体类型,Tkey为主键类型。
Microsoft.AspNetCore.Identity.EntityFrameworkCore
//每次直接使用IdentityUser都要说明主键类型,所以直接继承
//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来对实体类进行配置
//泛型参数分别代表用户类型、角色类型、主键类型
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);
}
}
可以通过这个类操作数据库,但是标识框架提供了RoleManager
、UserManager
类简化对数据库的操作,这些类封装了对IdentityDbContext的操作。
标识框架中的方法有执行失败的可能,所以有些方法可以通过Task
的返回结果来验证是否失败,IdentityResult的Succeeded
属性表示是否操作成功,如果失败,则可以从Errors
属性中获取错误信息,
RoleManager常用方法:
方法 | 说明 |
---|---|
Task | 创建角色 |
Task | 删除角色 |
Task | 指定名字的角色是否存在 |
Task | 根据角色名字获取角色对象 |
UserManager常用方法:
方法 | 说明 |
---|---|
Task | 创建用户 |
Task | 更新用户 |
Task | 删除用户 |
Task | 根据Id查找用户 |
Task | 根据name查找用户 |
Task | 检查用户密码是否正确,如果失败则调用AccessFailedAsync记录失败次数 |
Task | 修改密码 |
Task | 生成令牌,用来重置密码 |
Task | 重置密码 |
Task | 为用户增加角色 |
Task | 为用户删除角色 |
Task | 用户所拥有的所有角色 |
Task | 判断用户是否具有某个角色 |
Task | 判断用户是否被锁定 |
Task | 获取锁定时间 |
Task | 设置用户锁定时间 |
Task | 记录用户登陆失败次数,多次失败应当锁定一段时间 |
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>>();
Add-Migration
,Update-database
生成数据库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();
}
}
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();
}