要启动身份认证组件,首先要更改数据库上下文中继承的关系,原本我们是继承于
Dbcontext这个类,现在我们需要继承IdentityDbContext
继承这个类,我们需要安装对应的框架,
如图:
这里注意,版本一定要和你.net core 框架版本一致,不然不匹配。
框架安装完成后,我们进入数据库上下文类引入框架。
接着,将DbContext替换为IdentityDbContext
其中IdentityDbContext里的泛型IdentityUser就是身份认证的数据库结构,相当于UserModel也就是用户模型,里面有自定义好的相关用户信息字段。比如ID,姓名,性别什么的。这些定义都是由.net core 自动完成的,也会映射到数据库中。有了数据库结构后,IdentityDbContext会自动为我们的系统添加与用户表相对应的映射关系,如果数据库的用户表,UserTable还不存在,IdentityDbContext可以帮我们更新数据库,自动为我们添加用户表,这样通过数据库上下文对象,我们系统就可以自动获取到用户的信息了。而我们不需要为实现用户模块写一丁点代码。
对于IdentityUser如果默认结构不能满足你的需求,你也可以通过继承IdentityUser去修改用户模型。
补充点:记得初始化构造函数
public AppDBcontext(DbContextOptions<AppDBcontext> options) : base(options)
{
}
好了,接着
在创建数据库之前,我们需要给框架注册服务认证的依赖。
我们找到Startup.cs文件
在ConfigureServices这个函数中添加函数依赖,代码如下:
///注册身份认证的服务依赖
///泛型俩个参数分别是用户数据模型和角色模型
///AddEntityFrameworkStores 通过这个函数连接数据库上下文对象 泛型为对应的上下文对象。
services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<AppDbContext>();
框架注入成功后,我们来进行数据更新。
这里使用.net 的命令进行数据迁移,不会数据迁移的请看这篇文章
在项目根目录下 进行CMD
输入一下命令 进行数据迁移
执行完成后我们可以在项目中看到对应的项目迁移代码。
我们可以在代码中的UP函数中看到,代码自动编写了大概创建了八张表。可能很多人有疑问了,我明明没有添加这些表,为什么系统给我加了这么多乱七八糟的表呢,
我们打开数据库上下文,其实就是因为你继承了IdentityDbContext所以也就继承了IdentityDbContext自带的数据模型,以及数据模型所对应的数据库表,接下来让我回到命令行,执行更新数据库。
输入红线代码进行数据库更新。
执行完成后我们可以去数据库看到更新的表
这些表就是我们用户模块的新表。现在项目的用户数据就创建完成了。
接着我们试着用这写数据库,做一个注册功能。
首先创建一个接收注册消息的Dto
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
namespace Fakexiecheng.API.Dtos
{
public class RegisterDto
{
[Required]//Required注释 为必填字段
public string Email { get; set; }
[Required]
public string Password { get; set; }
[Required]
[Compare(nameof(Password),ErrorMessage ="输入的密码不一致")]
public string CofirmPassword { get; set; }
}
}
接着我们在对应的Api里写好对应的注册函数
private readonly UserManager<IdentityUser> _userManager;//可以通过这个工具对密码进行加密 泛型为定义的用户模型
/// <summary>
/// 用户注册
/// </summary>
/// <param name="registerDto">注册信息</param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] RegisterDto registerDto)
{
//1 使用用户名创建用户对象
var user = new IdentityUser()
{
UserName = registerDto.Email,
Email = registerDto.Email
};
//2 hash密码,保存用户
var result = await _userManager.CreateAsync(user, registerDto.Password);//hash密码并连同用户模型对象 一起保存打数据库中
//如果成功表示 用户创建成功并且保存起来了
//不成功
if (!result.Succeeded) {
//返回400
return BadRequest();
}
//3 return
//成功 200
return Ok();
}
其中hash密码的时候,我们用的Identity自带的工具 UserManager。代码中都有注释 自己看吧,
接着我们来试着用Postman进行请求一下。
可以看到没什么问题,而且UserManager可以帮你自动把用户名是否相同的验证都做了,就是CreateAsync用它哈希加密后 貌似不能解密。可能我没找到方法吧,而且UserManager对密码做的有限制,看我上面图密码搞那么复杂 你应该就懂了,太简单,是无法注册的。。。内部是如何验证的我也不知道,,,这个还等着网友们去探秘。可以下载微软自己提供的反编译工具,叫什么来着 ,忘记了,自己可以看看CreateAsync这个方法的内部结构,相信你可以找到答案,,,
接着把数据库接入登录系统中
首先用Identity框架下的SignInManager来处理用户登录验证,同时使用UserManager获取用户信息,并且提取用户权限,并转换为Claim使用JWT输出。
首先我们在控制器中注入服务依赖。
添加划线的代码,.net core 服务注入都是通过构造函数实现的,这个我想大家都知道吧。
接下来我们使用SignInManager来进行用户验证,
登录方法中加入一下代码:
[AllowAnonymous]//允许所有人访问
[HttpPost("login")]
public async Task<IActionResult> login([FromBody] LoginDto loginDto)
{
//1.进行信息认证
var loginResult = await _signInManager.PasswordSignInAsync(loginDto.Email,loginDto.password
, false //指示在关闭浏览器后登录 Cookie 是否应该保留的标志。
, false//多次登录失败后 是否锁定账号
);
//判断是否验证成功
if (!loginResult.Succeeded)
{
// 400
return BadRequest();
}
//获取用户信息
var user = await _userManager.FindByNameAsync(loginDto.Email);
//2.创建JWT Token
//header singningAlgorithm储存编码算法
var singningAlgorithm = SecurityAlgorithms.HmacSha256;
//payload 需要用到的数据
var claims = new List<Claim> {
// sub ==jwt的ID
//等同于 Sub:fake_user_id
new Claim(JwtRegisteredClaimNames.Sub,user.Id),
// new Claim(ClaimTypes.Role,"Admin")//角色信息
};
//获取用户角色
var roleNames = await _userManager.GetRolesAsync(user);
//遍历角色 一个用户可能有多个角色
foreach (var roleName in roleNames)
{
var roleClaim = new Claim(ClaimTypes.Role, roleName);
claims.Add(roleClaim);
}
//signature 数字签名 需要用到私钥
//私钥一般放在配置文件中 私钥是自定义的 想写什么写什么
//使用utf进行编码
var secretByte = Encoding.UTF8.GetBytes(_configuration["Authentication:SecretKey"]);
//使用非对称算法 对私钥加密
var signingkey = new SymmetricSecurityKey(secretByte);
//通过256验证非对称加密的私钥
var signingCredentials = new SigningCredentials(signingkey, singningAlgorithm);
//创建token
var token = new JwtSecurityToken(
issuer:_configuration ["Authentication:Issuer"],//谁发布的TOken
audience:_configuration["Authentication:Audience"],//token发布给谁
claims,//payload数据
notBefore:DateTime.UtcNow,//发布时间
expires:DateTime.UtcNow.AddDays(1),//有效时间
signingCredentials//数字签名
);
//以字符串形式 输出Token
var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
//3.返回jwt字符串
return Ok(tokenStr);
}
接下来我们来postman测试一下。
首先注册用户
然后用注册的用户登录系统
然后通过返回的JWT来进行一下数据访问
我们发现404
其实这里有个天大的坑。
原因:在我们使用Identity框架的多角色验证时,验证中间件使用的并不是JWT验证,和默认验证并不匹配。
解决办法
显示指定使用JWT 的验证方式。
再要访问的所有需要权限认证方法头部加上一下代码:
[Authorize(AuthenticationSchemes = "Bearer")]
接着测试一下
这下可以看到 返回403权限不足而不是404了 这是因为我规定了只有管理员才可以创建东西。所以这是正确的。
接着我们来定制一下用户模型
场景:当我们用的默认用户模型字段不够的时候,我么可以通过继承来定制一个用户模型。比如下图中
自带的这些字段不够用呢,就可以通过定制来扩展、
我们来看看数据库结构
表示用户模型
用于保存直接与用户权限绑定的声明,比如只允许张三可以删除数据。
用于第三方登录工具,比如记录微信登录的信息。
用于保存用户角色信息,一个用户可能有多个角色
记录用户登录的session,比如登录时长,拉黑用户都在这个表记录。
接着我们从代码的角度重载一下这几张表,首先新建一个ApplicationUser类继承IdentityUser,然后添加一下代码,这里切记 代码一定要一致,因为名称要和父类名称一致,不然会失败。
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Fakexiecheng.API.Models
{
public class ApplicationUser:IdentityUser
{
public string Address { get; set; }
//shoppingCart
//orders
public virtual ICollection<IdentityUserRole<string>> UserRoles { get; set; }
public virtual ICollection<IdentityUserClaim<string>> Claims { get; set; }
public virtual ICollection<IdentityUserLogin<string>> Logins { get; set; }
public virtual ICollection<IdentityUserToken<string>> Tokens { get; set; }
}
}
然后我们把用到的IdentityUser全部替换为ApplicationUser
接着我们可以注入一下种子数据进行数据的更新、
接着我进入上下文关系文档,在OnModelCreating函数中进行种子数据的初始化。
添加如下代码:
//初始化用户与角色的种子数据
//1.更新用户与角色的外键
//HasMany 表示一对多的关系 这个关系可以被映射为roles 每一个role都有一个外键关系 WithOne 而这个外键关系使用的是UserID 这个ID是必须的 所以加上IsRequired()
modelBuilder.Entity<ApplicationUser>(u => u.HasMany(x => x.UserRoles).WithOne().HasForeignKey(ur => ur.UserId).IsRequired());
//2.添加管理员角色
//给角色添加主键
var adminRoleId = Guid.NewGuid().ToString();
modelBuilder.Entity<IdentityRole>().HasData(
new IdentityRole()
{
Id = adminRoleId,
Name = "Admin",
NormalizedName = "Admin".ToUpper()//大写
}
);
//3.添加用户
var adminUserID = Guid.NewGuid().ToString();
ApplicationUser adminUser = new ApplicationUser
{
Id = adminUserID,
UserName = "13011@qq.com",
NormalizedUserName = "13011@qq.com".ToUpper(),
Email= "13011@qq.com",
NormalizedEmail= "13011@qq.com".ToUpper(),
TwoFactorEnabled=false,
EmailConfirmed=true,
PhoneNumber="1234567891",
PhoneNumberConfirmed=false
};
//hash密码
var ph = new PasswordHasher<ApplicationUser>();
adminUser.PasswordHash = ph.HashPassword(adminUser, "Fake123$");
modelBuilder.Entity<ApplicationUser>().HasData(adminUser);
//4.给用户加入管理员角色
modelBuilder.Entity<IdentityUserRole<string>>().HasData(new IdentityUserRole<string>(){
RoleId=adminRoleId,
UserId=adminUserID
});
然后我们进行数据迁移。
然后我们去数据迁移文件里解读一下代码
我们可以看到 ,添加了对应的数据和字段。
接着我们去用一下我们的种子数据。
我们发现可以成功登录并且创建数据。好了 就更新这么多了。。。。写的有点乱,希望不要介意。