标识框架(Identity)

Authentication与Authorization

1、Authentication对访问者的用户进行验证,“用户是否登录”。
2、Authorization验证访问者的用户身份是否有对资源访问的访问权限,“用户是否有权限访问地址”
用户可能有多个角色,一个角色可能有多个用户。

标识框架概念

1、Identity采用基于角色的访问控制(RBAC)策略,内置了对用户、角色等表的管理,以及相关的接口,支持外部登录、2FA(两阶段验证)等。
2、标识框架使用EFCore对数据库进行操作,因此标识框架几乎支持所有数据库。

简单使用

1、创建用户类和角色类,一般不自己编写而是创建继承IdentityUser<TKey>、IdentityRole<TKey>的自定义类类,TKey代表主键的类型,可以增加自定义属性。
2、NuGet安装:
Microsoft.AspNetCore.Identity.EntityFrameworkCore。
3、创建继承自IdentityDbContext的类,这个类内置了对IdentityUser<TKey>、IdentityRole<TKey>的处理,同时提供了基本的增删改查的操作。
4、可以通过IdentityDbContext类来操作数据库,不过框架中提供了RoleManager、UserManager等类来简化对数据库的操作。
5、部分方法的返回值为Task<IdentityResult>类型,这个类型包含着操作是否成功与错误信息等说明,需要手动判断
记得执行数据库迁移命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//自定义Identity角色
public class MyRole : IdentityRole<long>{
}
//自定义Identity用户
public class MyUser: IdentityUser<long> {
public string? WeiXinAccount { get; set; }
}
//数据库连接
//不要忘了<MyUser,MyRole,long>
public class MyDbContext :IdentityDbContext<MyUser,MyRole,long>{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options) {
}
}
//数据库迁移时(Add-Migration Init报错)如果报错就定义这个类
public class DbContextDesignTimeFactory : IDesignTimeDbContextFactory<MyDbContext> {
public MyDbContext CreateDbContext(string[] args) {
DbContextOptionsBuilder<MyDbContext> builder = new DbContextOptionsBuilder<MyDbContext>();
var serverVersion = new MySqlServerVersion(new Version(8, 0, 29));
builder.UseMySql("server=localhost;user=root;password=your_password;database=your_database", serverVersion);
return new MyDbContext(builder.Options);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Program.cs
//Identity
builder.Services.AddDbContext<MyDbContext>(opt => {
string constr = "server=localhost;user=root;password=your_password;database=your_database";
var serverVersion = new MySqlServerVersion(new Version(8, 0, 29));
opt.UseMySql(constr, serverVersion);
});
builder.Services.AddDataProtection();
builder.Services.AddIdentityCore<MyUser>(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;//邮箱配置
});
IdentityBuilder idBuilder = new IdentityBuilder(typeof(MyUser), typeof(MyRole), builder.Services);
idBuilder.AddEntityFrameworkStores<MyDbContext>()
.AddDefaultTokenProviders()
.AddUserManager<UserManager<MyUser>>()
.AddRoleManager<RoleManager<MyRole>>();
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
50
51
52
53
54
55
56
57
58
59
//Controller
[Route("[controller]/[action]")]
[ApiController]
public class DemoController : ControllerBase {
private readonly UserManager<MyUser> userManager;
private readonly RoleManager<MyRole> roleManager;
private IWebHostEnvironment env;

public DemoController(UserManager<MyUser> userManager, RoleManager<MyRole> roleManager, IWebHostEnvironment env) {
this.userManager = userManager;
this.roleManager = roleManager;
this.env = env;
}

[HttpPost]
//创建用户
public async Task<ActionResult<string>> Test1() {
if(await roleManager.RoleExistsAsync("admin")==false) {
MyRole role = new MyRole() { Name = "admin"};
var result = await roleManager.CreateAsync(role);
if (!result.Succeeded) return BadRequest("roleManager,CreateAsync failed");
}
MyUser user1 =await userManager.FindByNameAsync("LJH");
if(user1==null) {
user1 = new MyUser { UserName = "LJH" };
var result = await userManager.CreateAsync(user1, "123456");
if (!result.Succeeded) return BadRequest("userManager,CreateAsync failed");
}
if(! await userManager.IsInRoleAsync(user1, "admin")) {
var result = await userManager.AddToRoleAsync(user1, "admin");
if (!result.Succeeded) return BadRequest("userManager,AddToRoleAsync failed");
}
return "ok";
}
[HttpPost]
public async Task<ActionResult> CheckPwd(CheckPwdRequest req) {
string userName = req.UserName;
string pwd = req.Password;
var user = await userManager.FindByNameAsync(userName);
if(user==null) {
if(env.IsDevelopment()) {
return BadRequest("用户名不存在");
} else {
return BadRequest();//更安全
}
}
if(await userManager.IsLockedOutAsync(user)) {
return BadRequest("用户已被锁定,锁定结束时间"+user.LockoutEnd);
}
if(await userManager.CheckPasswordAsync(user, pwd)) {
await userManager.ResetAccessFailedCountAsync(user);
return Ok("登录成功");
}
else {
await userManager.AccessFailedAsync(user);
return BadRequest("用户名或密码错误");
}
}
}

密码重置

流程:
1、生成重置Token
2、Token发给客户,形式:连接、验证码
3、根据Token实现密码重置

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
[HttpPost]
public async Task<ActionResult> SendRestPasswordToken(string userName) {
var user = await userManager.FindByNameAsync(userName);
if(user==null) {
return BadRequest("用户名不存在");
}
string token = await userManager.GeneratePasswordResetTokenAsync(user);
Console.WriteLine("验证码是" + token);
return Ok();
}
[HttpPut]
public async Task<ActionResult> ResetPassword(string userName,string token,string newPassword) {
var user = await userManager.FindByNameAsync(userName);
if (user == null) {
return BadRequest("用户名不存在");
}
var result = await userManager.ResetPasswordAsync(user, token, newPassword);
if(result.Succeeded) {
await userManager.ResetAccessFailedCountAsync(user);
return Ok("密码重置成功");
} else {
await userManager.ResetAccessFailedCountAsync(user);
return BadRequest("密码重置失败");
}
}