七,标识框架identity
1.Asp.Net Core鉴权授权
在一个系统中,不是所有功能都能被自由地访问的,比如有的功能需要注册用户才能访问,有的功能需要VIP用户才能访问,针对资源的访问限制有两个概念:Authentication与Authorization,即鉴权与授权
- Authentication:用来对访问者的用户身份进行验证;
- Authorization:用来验证访问者的用户身份是否有对资源进行访问的权限。
通俗来说,Authentication是用来验证“用户是否登录成功”的,Authorization是用来验证“用户是否有权限访问”的。
2.标识框架identity
大部分系统中都需要通过数据库保存用户、角色等信息,并且需要注册、登录、密码重置、角色管理等功能。ASP.NET Core提供了标识(identity)框架,它采用RBAC(role-based access control,基于角色的访问控制)策略,内置了对用户、角色等表的管理及相关的接口,从而简化了系统的开发。标识框架还提供了对外部登录的支持。
标识框架中提供了IdentityUser<TKey>
、IdentityRole<TKey>
两个实体类型,其中的TKey代表主键的类型,因此IdentityUser<Guid>
就代表使用Guid类型主键的用户实体类
创建项目:
- 创建ASP.NET Core WebAPI项目,并通过NuGet安装
Microsoft.AspNetCore.Identity.EntityFrameworkCore
- 创建用户实体类User和角色实体类Role
public class User : IdentityUser<long>
{
public DateTime CreationTime { get; set; }
public string? NickName { get; set; }
}
public class Role : IdentityRole<long>
{
}
- 创建继承自IdentityDbContext的类,这是一个EFCore中的上下文类,我们可以通过这个类操作数据库,IdentityDbContext是一个泛型类,有3个泛型参数,分别代表用户类型、角色类型和主键类型
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);
}
}
- 我们需要向依赖注入容器中注册与标识框架相关的服务,并且对相关的选项进行配置
//标识框架配置
IServiceCollection services = builder.Services;
//对IdDbContext进行配置
services.AddDbContext<IdDbContext>(opt =>
{
string connStr = builder.Configuration.GetConnectionString("conn");
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);
//因为UserManager、RoleManager等服务被创建的时候需要注入非常多的服务,
//所以我们在使用标识框架的时候也需要注入和初始化非常多的服务
idBuilder.AddEntityFrameworkStores<IdDbContext>()
.AddDefaultTokenProviders()
.AddRoleManager<RoleManager<Role>>()
.AddUserManager<UserManager<User>>();
var app = builder.Build();
- 还需安装
Microsoft.EntityFrameworkCore.SqlServer
包。并在配置文件中写入数据库连接字符串
"ConnectionStrings": {
"conn": "Data Source=.;Database=IdentityTest;User ID=sa;Password=123456;MultipleActiveResultSets=True;Trusted_Connection=True;Encrypt=True;TrustServerCertificate=True;"
}
- 通过安装
Microsoft.EntityFrameworkCore.Tools
包。然后执行数据库迁移Add-Migration init
、Update-database
等命令执行EF Core的数据库迁移,然后程序就会在数据库中生成多张数据库表
- 编写控制器的代码。我们在控制器中需要对角色、用户进行操作,也需要输出日志,因此通过控制器的构造方法注入相关的服务
[ApiController]
[Route("[controller]/[action]")]
public class TestController : Controller
{
private readonly ILogger<TestController> _logger;
private readonly UserManager<User> _userManager;
private readonly RoleManager<Role> _roleManager;
public TestController(ILogger<TestController> logger, UserManager<User> userManager,
RoleManager<Role> roleManager)
{
_logger = logger;
_userManager = userManager;
_roleManager = roleManager;
}
......
}
- 编写创建角色和用户的方法。
[HttpPost]
public async Task<IActionResult> CreateUserRole()
{
bool roleExists = await _roleManager.RoleExistsAsync("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 _userManager.FindByNameAsync("lf");
if (user == null)
{
user = new User
{
UserName = "zhmen",
Email = "206615243@qq.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();
}
- 编写处理登录请求的操作方法Login。
public record LoginRequest(string UserName, string Password);
[HttpPost]
public async Task<IActionResult> Login(LoginRequest loginRequest)
{
string userName = loginRequest.UserName;
string password = loginRequest.Password;
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
return NotFound($"用户名{userName}不存在!");
}
var islocked = await _userManager.IsLockedOutAsync(user);
if (islocked)
{
return BadRequest("用户已锁定!");
}
var success = await _userManager.CheckPasswordAsync(user, password);
if (success)
{
return Ok();
}
else
{
var r = await _userManager.AccessFailedAsync(user);
if (!r.Succeeded)
{
return BadRequest("访问失败信息写入错误!");
}
else
{
return BadRequest("失败!");
}
}
}
最后,整体的TestController
方法
using IdentityTest.Entity;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace IdentityTest.Controllers
{
[ApiController]
[Route("[controller]/[action]")]
public class TestController : Controller
{
private readonly ILogger<TestController> _logger;
private readonly UserManager<User> _userManager;
private readonly RoleManager<Role> _roleManager;
public TestController(ILogger<TestController> logger, UserManager<User> userManager,
RoleManager<Role> roleManager)
{
_logger = logger;
_userManager = userManager;
_roleManager = roleManager;
}
//编写创建角色和用户的方法
[HttpPost]
public async Task<IActionResult> CreateUserRole()
{
bool roleExists = await _roleManager.RoleExistsAsync("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 _userManager.FindByNameAsync("zhmen");
if (user == null)
{
user = new User
{
UserName = "zhmen",
Email = "2066015243@qq.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();
}
//编写处理登录请求的操作方法Login
[HttpPost]
public async Task<IActionResult> Login(LoginRequest loginRequest)
{
string userName = loginRequest.UserName;
string password = loginRequest.Password;
var user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
return NotFound($"用户名{userName}不存在!");
}
var islocked = await _userManager.IsLockedOutAsync(user);
if (islocked)
{
return BadRequest("用户已锁定!");
}
var success = await _userManager.CheckPasswordAsync(user, password);
if (success)
{
return Ok();
}
else
{
var r = await _userManager.AccessFailedAsync(user);
if (!r.Succeeded)
{
return BadRequest("访问失败信息写入错误!");
}
else
{
return BadRequest("失败!");
}
}
}
}
}