Authentication与Authorization

1.Authentication对访问者的用户身份进行验证,“用户是否登陆成功”。
2.Authorization验证访问者用户身份是否又对资源访问的访问权限,“用户是否有权限访问这个地址”。

标识(Identity)框架

1.标识(Identity)框架:采用基于角色的访问控制(Role-Based Access Control,简称RBAC)策略,内置了对用户、角色等表的管理以及相关的接口,支持外部登录、2FA等。
2.标识框架使用EF Core对数据进行操作,因此标识框架支持几乎所有数据库。

Identity框架使用

1.IdentityUser<TKey>、IdentityRole<TKey>,TKey代表主键的类型。我们一般编写继承自IdentityUser<TKey>、IdentityRole<TKey>等的自定义类,可以增加自定义属性。
2.NuGet安装
Microsoft.AspNetCore.Identity.EntityFrameworkCore
3.创建继承自IdentityDbContext的类。
4.可以通过IdDbContext类来操作数据库,不过框架中提供了RoleManager、UserManager等类来简化对数据库的操作。
5.部分方法的返回值为Task<IdentityResult>类型,查看、讲解IdentityResult类型定义。

  1. 创建用户实体类User和角色实体类Role. 分别继承自IdentityUser<long>、IdentityRole<long>的User类和Role类。
1
2
3
4
5
public class User:IdentityUser<long>
{
public DateTime CreationTime { get; set; }
public string? NickName { get; set; }
}
1
2
3
public class Role:IdentityRole<long>
{
}

IdnetityUser中定义类了很多属性。

除了IdentityUser和IdentityRole之外,表示框架中还有很多其他实体类。

  1. 创建继承自IdentityDbContext的类,这是一个EF Core中的上下文类,我们可以通过这个类操作数据库。IdentityDbContext是一个泛型类,有3个泛型参数,分别代表用户类型、
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IdDbContext:IdentityDbContext<User,Role,long>
{
public IdDbContext(DbContextOptions<IdDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
new UserConfig().Configure(builder.Entity<User>());
new RoleConfig().Configure(builder.Entity<Role>());
}
}
  1. 向依赖注入容器中注册与标识框架相关的服务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
builder.Services.AddDbContext<IdDbContext>(opt =>
{
string connStr = builder.Configuration.GetSection("Default").Value;
opt.UseSqlServer(connStr);
});

builder.Services.AddDataProtection();
builder.Services.AddIdentityCore<User>(opt =>
{
opt.Password.RequireDigit = false; // 是否需要数字
opt.Password.RequireLowercase = false; // 是否需要小写字母
opt.Password.RequireNonAlphanumeric = false; // 是否需要特殊字符
opt.Password.RequireUppercase = false; // 是否需要大写字母
opt.Password.RequiredLength = 6; // 密码长度
opt.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider; // 重置密码的token提供者
opt.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider; // 邮箱确认的token提供者
});
var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), builder.Services);
idBuilder.AddEntityFrameworkStores<IdDbContext>()
.AddDefaultTokenProviders().AddRoleManager<RoleManager<Role>>()
.AddUserManager<UserManager<User>>();
  1. 编写控制器的代码。在控制器中需要对角色、用户进行操作,因此通过控制器的构造方法注入相关的服务。
1
2
3
4
5
6
7
8
9
10
11
public class DemoController : ControllerBase
{
private readonly RoleManager<Role> roleConfig;
private readonly UserManager<User> userConfig;

public DemoController(RoleManager<Role> roleConfig, UserManager<User> userConfig)
{
this.roleConfig = roleConfig;
this.userConfig = userConfig;
}
}
  1. 编写创建角色和用户的方法CreateUserRole.
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
[HttpGet]
public async Task<ActionResult<string>> Test1()
{
bool roleExists = await roleConfig.RoleExistsAsync("admin"); //判断角色是否存在
if (!roleExists)
{
Role role = new Role { Name = "YOUXIANYU" };
var r = await roleConfig.CreateAsync(role);
if (!r.Succeeded)
{
return BadRequest(r.Errors);
}
}
User user = await this.userConfig.FindByNameAsync("admin"); //查找用户
if (user == null)
{
user = new User { UserName = "YOUXIANYU", Email = "84289579@QQ.COM", EmailConfirmed = true }; //创建用户
var r = await userConfig.CreateAsync(user,"123456");
if (!r.Succeeded)
{
return BadRequest(r.Errors);
}
r = await userConfig.AddToRoleAsync(user, "YOUXIANYU"); //添加角色
if (!r.Succeeded)
{
return BadRequest(r.Errors);
}
}
return Ok("ok");
}
  1. 检查登录用户信息
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
[HttpPost]
public async Task<ActionResult<string>> CheckPwd(CheckPwdRequest req)
{
string userName = req.userName;
string password = req.password;
var user = await userConfig.FindByNameAsync(userName); //查找用户
if(user == null)
return NotFound($"用户{userName}不存在");
if(await userConfig.IsLockedOutAsync(user))
return BadRequest("用户已锁定");
var success = await userConfig.CheckPasswordAsync(user, password); //检查密码
if (success)
{
await userConfig.ResetAccessFailedCountAsync(user); //重置失败次数
return Ok("密码正确");
}
else
{
await userConfig.AccessFailedAsync(user); //增加失败次数
var count = await userConfig.GetAccessFailedCountAsync(user); //获取失败次数
if (count >= 5)
{
await userConfig.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddMinutes(5)); //锁定用户
return BadRequest("用户已锁定");
}
return BadRequest($"密码错误,还有{5 - count}次机会");
}
}

实现密码的重置

1.生成重置Token
2.Token发给客户(邮件、短信等),形式:连接、验证码等。
3.根据Token完成密码的重置。

编写一个发送重置密码请求的操作方法SendResetPasswordToken.

1
2
3
4
5
6
7
8
9
10
[HttpPost]
public async Task<ActionResult> SendResetPasswordToken(string userName)
{
var user = await userConfig.FindByNameAsync(userName); //查找用户
if(user == null)
return NotFound($"用户{userName}不存在");
string token = await userConfig.GeneratePasswordResetTokenAsync(user); //生成重置密码的token
Console.WriteLine($"验证码是{token}");
return Ok("ok");
}

调用GeneratePasswordResetTokenAsyncc方法来生成一个密码重置令牌,这个令牌会保存到数据库中,然后我们把这个令牌发送到用户邮箱。

编写重置密码的操作方法VerifyResetPasswordToken。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[HttpPut]
public async Task<ActionResult> ResetPassword(string userName,string token,string newPassword)
{
var user = await userConfig.FindByNameAsync(userName); //查找用户
if (user == null)
return NotFound($"用户{userName}不存在");
var r = await userConfig.ResetPasswordAsync(user, token, newPassword); //重置密码
if(r.Succeeded)
{
await userConfig.ResetAccessFailedCountAsync(user);
return Ok("重置密码成功");
}
else
{
await userConfig.AccessFailedAsync(user); //增加失败次数
return BadRequest("重置密码失败");
}
}