十年河东,十年河西,莫欺少年穷

学无止境,精益求精

微软的Identity框架虽说只有有部分公司用,但还是有必要学学的。

本篇以NetCore3.1做演示

接着上篇博客:​​Net6/NetCore3.1搭建codeFirst 【支持多dbcontext】并接合TransactionScope 完成事务操作​

上节博客搭建了一个CodeFirst,我们继续使用这个项目

1、 在DbContext项目引用程序集

Microsoft.AspNetCore.Identity.EntityFrameworkCore

2、 在DbContext项目中新建用户表、角色表

用户表

using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Text;

namespace CoreDbContext.DbDtos
{
//IdentityUser<T> T 是主键的类型
public class User : IdentityUser<string>
{
//可以自定义一些字段
//public string WeiXinNumber { get; set; }
}
}

角色表

using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Text;

namespace CoreDbContext.DbDtos
{
//IdentityUser<T> T 是主键的类型
public class UserRole : IdentityRole<string>
{

}
}

3、配置生成的用户表和角色表名称【也可选择不配置】

internal class UserConfig : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("T_Users");
}
}


internal class UserRoleConfig : IEntityTypeConfiguration<UserRole>
{
public void Configure(EntityTypeBuilder<UserRole> builder)
{
builder.ToTable("T_Roles");
}
}

 4、让数据库上下文继承自IdentityDbContext

Net6 EfCore + Identity权限框架入门_ide

Net6 EfCore + Identity权限框架入门_ide_02

/// <summary>
/// add-Migration -Context 可以通过-Context 指定上下文,因此,项目中可以有多个DbCOntext,这样就支持多个数据库
/// </summary>
public class swapDbContext: IdentityDbContext<User, UserRole, string>
{
public DbSet<book> books { get; set; }

public swapDbContext(DbContextOptions<swapDbContext> options) : base(options)
{
}


protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//从当前程序集命名空间加载所有的IEntityTypeConfiguration
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}

/// <summary>
/// 开发环境专用 用于add-Migration 时使用
/// </summary>
public class swapDbContextFactory : IDesignTimeDbContextFactory<swapDbContext>
{
public swapDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<swapDbContext>();
optionsBuilder.UseSqlServer("Data Source=LAPTOP-84R6S0FB;Initial Catalog=swap;Integrated Security=True;MultipleActiveResultSets=true");

return new swapDbContext(optionsBuilder.Options);
}
}

View Code

4.1、【重点】对Identity做全局配置

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<swapDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("swapDbContext")), ServiceLifetime.Scoped);


#region Identity 配置
services.AddDataProtection();
//不要用 AddIdentity , AddIdentity 是于MVC框架中的
services.AddIdentityCore<User>(opt =>
{
opt.Password.RequireDigit = false; //数字
opt.Password.RequireLowercase = false;//小写字母
opt.Password.RequireNonAlphanumeric = false;//特殊符号 例如 ¥#@!
opt.Password.RequireUppercase = false; //大写字母
opt.Password.RequiredLength = 6;//密码长度 6
opt.Password.RequiredUniqueChars = 1;//相同字符可以出现几次
opt.Lockout.MaxFailedAccessAttempts = 5; //允许最多输入五次用户名/密码错误
opt.Lockout.DefaultLockoutTimeSpan = new TimeSpan(0, 5, 0);//锁定五分钟
opt.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider; // 修改密码使用邮件【验证码模式】
opt.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider; ////
});
var idBuilder = new IdentityBuilder(typeof(User), typeof(UserRole), services);
idBuilder.AddEntityFrameworkStores<swapDbContext>().AddDefaultTokenProviders().AddRoleManager<RoleManager<UserRole>>().AddUserManager<UserManager<User>>();
#endregion
}

5、迁移数据库

Add-migration initIndetity

update-database

Net6 EfCore + Identity权限框架入门_ide_03

 

 6、测试代码

需要说明的是,微软Identity框架会生成用户表【T_Users】、角色表【T_Roles】、用户角色关联表【AspNetUserRoles】、及其他表

其他的一些表,我没做研究,本篇博客仅供入门

Net6 EfCore + Identity权限框架入门_ide_04

 

 

1、id 主键
2、UserName 用户登录账户
3、NormalizedUserName 和用户登录账户类似,本篇博客不使用
4、Email 邮箱,用于找回密码
5、NormalizedEmail 和邮箱类似
6、EmailConfirmed 通过邮箱找回密码的次数
7、PasswordHash 用户密码Hash值,密文
8、SecurityStamp 不清楚
9、ConcurrencyStamp 不清楚
10、PhoneNumber 电话号码
11、PhoneNumberConfirmed 通过电话找回密码次数
12、TwoFactorEnabled 是否开启TwoFactor
13、LockoutEnd 账户被锁定的最终时间,过了这个时间自动解锁
14、LockoutEnabled 账户是否可用 例如输入3次密码错误,框架会自动锁定账户
15、AccessFailedCount 登录失败次数【例如达到三次后,账户被锁定】

T_Roles表 及 关联表很简单,不做介绍。

下面演示一个类似于github找回密码的示例。

6.1、创建用户、创建角色,赋给用户某个角色

[HttpGet]
public async Task<IActionResult> create()
{
var RoleName = "admin";
//检测角色是否已存在
var roleHas = await roleManager.RoleExistsAsync(RoleName);
if (roleHas)
{
return BadRequest("已存在角色,不可以重复创建");
}
UserRole role = new UserRole()
{
Name = RoleName,
Id = Guid.NewGuid().ToString()
};
//创建角色
var roleResutl = await roleManager.CreateAsync(role);
if (!roleResutl.Succeeded)
{
return BadRequest("创建角色失败");
}
//根据用户名查询用户
var olduser =await userManager.FindByNameAsync("jack");
if (olduser == null)
{
User user = new User()
{
UserName = "jack", //只能包含字母和数字
Id=Guid.NewGuid().ToString()
};
//创建用户
///123456 是用户密码
var userResult = await userManager.CreateAsync(user, "123456");
if (!userResult.Succeeded)
{
return BadRequest("创建用户失败");
}
//当前用户是否属于某个角色
var isinRole = await userManager.IsInRoleAsync(user, RoleName);
if (!isinRole)
{
//创建用户角色关联关系
var addResult = await userManager.AddToRoleAsync(user, RoleName);
if (!addResult.Succeeded)
{
return BadRequest("用户关联角色失败");
}
}
}

return Ok();

}

View Code

6.2、模拟登录

/// <summary>
/// 登录
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> login()
{
var userName = "jack";
var pwd = "123456";
var user =await userManager.FindByNameAsync(userName);
if (user == null)
{
return BadRequest();
}
//用户是否被锁定
var isLock = await userManager.IsLockedOutAsync(user);
if (isLock)
{
return BadRequest("用户已被锁定,锁定结束时间为:"+user.LockoutEnd);
}
//检测密码是否正确
var bol =await userManager.CheckPasswordAsync(user, pwd);
if (bol)
{
//记录登录失败 登录次数重置为0
await userManager.ResetAccessFailedCountAsync(user);
return Ok("登录成功");
}

else
{
//记录登录失败,登录失败次数加1
await userManager.AccessFailedAsync(user);
return BadRequest();
}


}

View Code

6.3、模拟找回密码,发送验证码到邮箱【代码只生产验证码】

Net6 EfCore + Identity权限框架入门_ide

Net6 EfCore + Identity权限框架入门_ide_02

/// <summary>
/// 获取重置密码时验证码
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> GetResetPwdToken()
{
var userName = "jack";
var user = await userManager.FindByNameAsync(userName);
if (user == null)
{
return BadRequest("用户名不存在");
}
var isLock = await userManager.IsLockedOutAsync(user);
if (isLock)
{
return BadRequest("用户已被锁定,锁定结束时间为:" + user.LockoutEnd);
}
//验证码
var Token = await userManager.GeneratePasswordResetTokenAsync(user);
Console.WriteLine("验证码为:" + Token);
return Ok(Token);
}

View Code

发送到邮箱的代码不做解答

6.4、用户收到验证码后,重置密码

/// <summary>
/// GetResetPwdToken 方法获取的验证码
/// </summary>
/// <param name="Token"></param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> ResetPwd(string Token)
{
var userName = "jack";
string newPassword = "654321";
var user = await userManager.FindByNameAsync(userName);
if (user == null)
{
return BadRequest("用户名不存在");
}
var isLock = await userManager.IsLockedOutAsync(user);
if (isLock)
{
return BadRequest("用户已被锁定,锁定结束时间为:" + user.LockoutEnd);
}
var result = await userManager.ResetPasswordAsync(user, Token,newPassword);
if (result.Succeeded)
{
//记录登录失败 登录次数重置为0
await userManager.ResetAccessFailedCountAsync(user);
return Ok("重置密码成功");
}
else
{
//次数多了 也要锁定用户,登录失败次数加1 防止一致重置密码
await userManager.AccessFailedAsync(user);
return BadRequest("重置密码失败");
}
}

View Code

至此,结束

7、【额外补充】单独创建IdentityContext并单独生成相关的表

在不修改原来的DbCOntext的情况下,我们需要单独创建一个类库,用于生成identity相关的实体数据库表

新增类库 IdentityCore

新增User 和 Role实体,和步骤2一致

新增IdentityDB数据库上下文

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;

namespace IdentityCore
{
public class AuthDbContext : IdentityDbContext<User, UserRole, string>
{
public AuthDbContext(DbContextOptions<AuthDbContext> options) : base(options)
{

}
}
}

View Code

注意:数据库迁移时,需要指定 -context 参数

context AuthDbContext

update-database -context AuthDbContext

实际开发项目中,我们上线后的项目,每次改动数据库时,也可以采用新建DbContext的办法进行,这样在更新线上数据库时,我们只需指定 -context参数就能更新新增加的数据表,这样做的好处是不会影响到已上线的数据库、表

@天才owl的博客