8.1标识框架
ASP.NET Core提供了标识框架,采用RBAC(基于角色的访问控制),内置了对用户、角色等表的管理及相关接口,框架中提供了IdentityUser<TKey>
和IdentityRole<TTKey>
两个实体类型,Tkey为主键类型。
使用步骤:
1. NuGet安装Microsoft.AspNetCore.Identity.EntityFrameworkCore
2. 创建用户实体类和角色实体类
//每次直接使用IdentityUser<long>都要说明主键类型,所以直接继承
//IdentityUser类中已经内置了用户名、密码、邮箱等属性,如果想增加自己的属性,也可以使用继承
publicclassUser : IdentityUser<long>
{
publicDateTimeCreationTime { get; set; }//增加创建时间和昵称两个自定义属性
publicstring?NickName { get; set; }
}
publicclassRole : IdentityRole<long>
{
}
除了IdentityUser和IdentityRole,标识框架中还有IdentityRoleClaim、IdentityUserToken等实体类,这些实体类都有默认的表名,如果要修改,可以使用IEntityTypeConfiguration来对实体类进行配置
3. 创建标识上下文类,继承自IdentityDbContext
//泛型参数分别代表用户类型、角色类型、主键类型
publicclassIdDbContext : IdentityDbContext<User, Role, long>
{
publicIdDbContext(DbContextOptions<IdDbContext>options)
: base(options)
{
}
protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
可以通过这个类操作数据库,但是标识框架提供了RoleManager
、UserManager
类简化对数据库的操作,这些类封装了对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. 向容器中注册服务
IServiceCollectionservices=builder.Services;
//对IdDbContext进行设置
services.AddDbContext<IdDbContext>(opt=> {
stringconnStr=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;
});
varidBuilder=newIdentityBuilder(typeof(User), typeof(Role), services);
//注册各种服务
idBuilder.AddEntityFrameworkStores<IdDbContext>()
.AddDefaultTokenProviders()
.AddRoleManager<RoleManager<Role>>()
.AddUserManager<UserManager<User>>();
5. 使用Add-Migration
,Update-database
生成数据库
6. 编写控制器代码,对角色、用户进行操作
publicclassTest1Controller : ControllerBase
{
privatereadonlyILogger<Test1Controller>logger;//注册日志
privatereadonlyRoleManager<Role>roleManager;
privatereadonlyUserManager<User>userManager;
publicTest1Controller(ILogger<Test1Controller>logger,
RoleManager<Role>roleManager, UserManager<User>userManager)
{
this.logger=logger;
this.roleManager=roleManager;
this.userManager=userManager;
}
[HttpPost]
publicasyncTask<ActionResult>CreateUserRole()
{
boolroleExists=awaitroleManager.RoleExistsAsync("admin");//判断admin账户是否存在
if (!roleExists)
{
Rolerole=newRole { Name="Admin"};
varr=awaitroleManager.CreateAsync(role);
if (!r.Succeeded)//框架会存在创建失败的情况,一般都要进行判断是否成功
{
returnBadRequest(r.Errors);
}
}
Useruser=awaitthis.userManager.FindByNameAsync("yzk");//查找用户
if (user==null)
{
//EmailConfirmed设置为true
//使用邮箱注册时,发送验证码到邮箱,用户输入验证码后才能确认这个邮箱可用,EmailConfirmed属性表示邮箱是否确认过
//如果邮箱确认是存在的,则可以像下面 这样直接使用
//如果创建用户的时候不确定邮箱是否可用,则需要先调用UserManager的GenerateEmailConfirmationTokenAsync创建
//一个字符串作为“确认令牌”,服务器将确认令牌发送到用户邮箱,用户在输入确认令牌的时候,调用UserManager的
//ConfirmEmailAsync方法来验证令牌
user=newUser{UserName="yzk",Email="yangzhongke8@gmail.com",EmailConfirmed=true};
varr=awaituserManager.CreateAsync(user, "123456");//创建用户
if (!r.Succeeded)
{
returnBadRequest(r.Errors);
}
r=awaituserManager.AddToRoleAsync(user, "admin");//增加角色
if (!r.Succeeded)
{
returnBadRequest(r.Errors);
}
}
returnOk();
}
}
7. 编写登陆请求的操作方法
publicrecordLoginRequest(stringUserName,stringPassword);
[HttpPost]
publicasyncTask<ActionResult>Login(LoginRequestreq)
{
stringuserName=req.UserName;
stringpassword=req.Password;
varuser=awaituserManager.FindByNameAsync(userName);
if (user==null)
{
returnNotFound($"用户名不存在{userName}");
}
if (awaituserManager.IsLockedOutAsync(user))
{
returnBadRequest("LockedOut");
}
varsuccess=awaituserManager.CheckPasswordAsync(user, password);//验证密码是否正确
if (success)
{
returnOk("Success");
}
else
{
//密码错误则记录一次登陆失败,达到次数后就锁定账户一段时间,防止暴力破解
//失败次数和锁定时间可以在AddIdentityCore中设定
//option.Lockout.DefaultLockoutTimesSpan和option.Lockout.MaxFailedAccessAttempts来修改
varr=awaituserManager.AccessFailedAsync(user);
if (!r.Succeeded)
{
returnBadRequest("AccessFailed failed");
}
returnBadRequest("Failed");
}
}
实现密码重置
发送重置密码的请求
publicrecordSendResetPasswordTokenRequest(stringEmail);
[HttpPost]
publicasyncTask<IActionResult>SendResetPasswordToken(
SendResetPasswordTokenRequestreq)
{
stringemail=req.Email;
varuser=awaituserManager.FindByEmailAsync(email);
if (user==null)
{
returnNotFound($"邮箱不存在{email}");
}
//生成密码令牌
stringtoken=awaituserManager.GeneratePasswordResetTokenAsync(user);
logger.LogInformation($"向邮箱{user.Email}发送Token={token}");
returnOk();
}
重置密码
publicrecordVerifyResetPasswordRequest(stringEmail,stringtoken,stringnewPassword);
publicasyncTask<IActionResult>VerifyResetPassword(
SendResetPasswordTokenRequestreq)
{
stringemail=req.Email;
varuser=awaituserManager.FindByEmail(email);
stringtoken=req.Token;
stringpassword=req.NewPassword;
varr=awaituserManager.ResetPasswordAsync(user,token,password);//重置密码
returnOk();
}