Identity framework:
1. Adopt the Role-Based Access Control (RABC) strategy, which has built-in management of user, role and other tables and related interfaces.
2. The Identity framework uses EF Core to operate the database.
Identity framework usage
Since the Identity framework uses EF Core to operate the database, the operation has similar expenditures as defining EF Core.
1. Define entity classes:
The Identity framework has helped us define two entity classes, IdentityUser
IdentityRole entity class: used to define role data. For example, different roles have different operation permissions.
/// <summary> /// Verify whether the user has permission to perform some operations /// </summary> public class MyRole : IdentityRole<long> {<!-- --> //IdentityRole entity also defines many columns, which can be added in subclasses }
IdentityUser entity class: user-defined user information, used to verify user login, etc.
//Verify whether the user is logged in public class MyUser : IdentityUser<long> {<!-- --> //Many columns have been defined in the IdentityUser entity public string? WeiChart {<!-- --> get; set; } }
2. Install the NuGet package used by the Identity framework
Install-Package microsoft.AspNetCore.Identity.EntityFrameworkCore
Identity framework Nuget package
3. Define entity configuration class
Like EF Core, define the configuration class of the entity. Of course, you can also not configure it here and just ask it to use the default configuration.
IdentityRole entity configuration class:
public class MyRoleConfig : IEntityTypeConfiguration<MyRole> {<!-- --> public void Configure(EntityTypeBuilder<MyRole> builder) {<!-- --> builder.ToTable("T_MyRole");//Specify the table name } }
IdentityUser entity configuration class:
public class MyUserConfig : IEntityTypeConfiguration<MyUser> {<!-- --> public void Configure(EntityTypeBuilder<MyUser> builder) {<!-- --> builder.ToTable("T_MyUser");//Specify the table name } }
4. Create the DbContext class of the Identity framework
Inherit the IdentityDbContext generic class, where the first generic is IdentityUser
//Inherit IdentityDbContext<MyUser, MyRole, long> //These three generic types need to be added after the inheritance class here. The first two are added to use the customized attributes in our entities, and the last one is the type of the primary key. //If you don't add it, it will default to your entity class being IdentityUser, IdentityRole, rather than the class after the inherited class. public class MyIdentityDbContext : IdentityDbContext<MyUser, MyRole, long> {<!-- --> //In order for me to call this DBContext in other assemblies, use the DbContextOptions type public MyIdentityDbContext(DbContextOptions dbContextOptions): base(dbContextOptions) {<!-- --> } protected override void OnModelCreating(ModelBuilder builder) {<!-- --> base.OnModelCreating(builder); builder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); } }
In EF Core, we all operate the database by instantiating the DbContext class, but in the Identity framework, we are provided with two generic types, UserManager
5. Call the Migration tool for migration
Because I put the configuration and definition of Identity for the database in a separate assembly, it is convenient for other assemblies to call and can use different databases more flexibly. There is no need to rewrite OnConfiguring(DbContextOptionsBuilder optionsBuilder) in the DbContext class. The database to be called is hard-coded. Although it is more flexible, it will be more troublesome for us to migrate. We need to define a separate class in the assembly to help us migrate. The operation is as follows
Calling the Migration toolkit
Install-Package Microsoft.EntityFrameworkCore.Tools
Define a class that inherits the IDesignTimeDbContextFactory<T> interface. T refers to the DbContext you need to migrate. Define your reference to the database in this class. Whatever database you use, just call the package corresponding to the database.
public class IdentityDesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyIdentityDbContext> {<!-- --> public MyIdentityDbContext CreateDbContext(string[] args) {<!-- --> DbContextOptionsBuilder<MyIdentityDbContext> dbContextOptionsBuilder = new DbContextOptionsBuilder<MyIdentityDbContext>(); //I am using the SqlServer database here, calling the Microsoft.EntityFrameworkCore.SqlServer package dbContextOptionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=demo1;Integrated Security=true;TrustServerCertificate=true"); MyIdentityDbContext myDBContext = new MyIdentityDbContext(dbContextOptionsBuilder.Options); return myDBContext; } }
The first premise is to declare a constructor with parameters of type DbContextOptions in DbContext, as shown in the fourth step above. In this way, you can use it here and register the service in other assemblies and write the configuration of the database, as shown in step 6 below.
//This is the constructor in the MyIdentityDbContext class in step 4, for demonstration only public MyIdentityDbContext(DbContextOptions dbContextOptions): base(dbContextOptions) {<!-- --> }
//Register the DbContextOptions service to write the configuration of the database in other assemblies. This is only for demonstration, specific to the beginning of the sixth step of the code. builder.Services.AddDbContext<MyIdentityDbContext>(options => {<!-- --> options.UseSqlServer("Data Source=.;Initial Catalog=demo1;Integrated Security=SSPI;TrustServerCertificate=true"); });
Set the assembly as a startup item, and then call the Add-Migration name, Update-Database and other commands in the package manager console (of course there are other commands that will not be described here)
After calling the Update-database command, if successful, go to the database to check whether the corresponding table has been generated.
6. Register the services required by Identity
Specifically as follows:
Note: The server configuration needs to be configured in Programs (as shown below) and cannot be configured in DbContext, otherwise an error will be reported for the subsequent service registration of the Identity framework.
builder.Services.AddDbContext(options =>
{
options.UseSqlServer(“Data Source=.;Initial Catalog=demo1;Integrated Security=SSPI;TrustServerCertificate=true”);
});
//Register Identity framework----------------------- //First register the DbContext written in other assemblies and write the configuration of the database, which is more flexible builder.Services.AddDbContext<MyIdentityDbContext>(options => {<!-- --> options.UseSqlServer("Data Source=.;Initial Catalog=demo1;Integrated Security=SSPI;TrustServerCertificate=true"); }); //Register for data protection builder.Services.AddDataProtection(); //builder.Services.AddIdentity(); This is generally used in MVC, rather than in front-end and back-end separation. It will provide some interface and other operations. //Here we need to add some changes to the login verification operation. For example, the entity I use to log in here is MyUser. builder.Services.AddIdentityCore<MyUser>(options => {<!-- --> //If you use his original password, it will be more troublesome. His original password requires you to create it very complicated, so the password is simplified here, or you don’t need to set it. //The following are some operations on passwords, etc. You can check the documentation. //Set the number of incorrect password inputs here and write a few examples. options.Lockout.MaxFailedAccessAttempts = 5;//Wrong entries several times to lock options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromSeconds(10);//Lock time // options.Password.RequireDigit = false; options.Password.RequireLowercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequiredLength = 6; /* PasswordResetTokenProvider: The reset password is the Token sent to you, which is the verification code. Notice: Here, when registering, set options.Tokens.PasswordResetTokenProvider to TokenOptions.DefaultEmailProvider, and the returned value is the value. options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider; The result of setting this default value is: 403797 The result of not setting this value: CfDJ8LYtD6oWUe1GqPBiQxTKiPC1v0835ryQmJ8kuK86CKme6Ik6FDhmfY1uAIf01HpxshNC0/Rv4FWbwf3PNE9TYQqtYt1lX8JHwJ1ekAk6b0H6qi6gAViIMxOUFwhy4c5hMc8Fziw 9r2plFzfnEwok3ps8TJaqyH + WVcNp9O4rsBvzBgg9g8dnFgC5qFi/Jvmfeg== When this value is not set, it is generally used when a link needs to be generated. */ options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider; options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider; }); //Next establish a relationship with the entity //Parameters: Login verification, role permission verification, service collection IdentityBuilder iBuilder = new IdentityBuilder(typeof(MyUser), typeof(MyRole), builder.Services); //Connect entities with DbContext: iBuilder.AddEntityFrameworkStores<MyIdentityDbContext>() //iBuilder.AddUserManager<UserManager<MyUser>>(): Add login verification management //iBuilder.AddRoleManager<RoleManager<MyRole>>(): Add role permission management //Add services to the UserManager and RoleManager classes here for our convenience iBuilder.AddEntityFrameworkStores<MyIdentityDbContext>().AddDefaultTokenProviders().AddUserManager<UserManager<MyUser>>().AddRoleManager<RoleManager<MyRole>>(); //Look at the above line of code. We knew before that we can directly instantiate DbContext to call the entities inside to complete the operation, but we don’t need to do this here. //Why .AddUserManager<UserManager<MyUser>>().AddRoleManager<RoleManager<MyRole>>(); is called later is because we can use the UserManager and UserManager classes provided by the Identity framework to operate the corresponding entities //----------------------------------------
7. Use UserManager and RoleManager to manipulate table data
The following is an example. When using the specific method, just check the documentation or go to the class to see it. The English meaning is the literal meaning.
[Route("api/[controller]/[action]")] [ApiController] public class ValuesController : ControllerBase {<!-- --> //Inject the required classes, there is no need to use DbContext here private readonly UserManager<MyUser> userManager; private readonly RoleManager<MyRole> roleManager; private readonly IHostEnvironment hostEn; public ValuesController(RoleManager<MyRole> roleManager, UserManager<MyUser> userManager, IHostEnvironment hostEn) {<!-- --> this.userManager = userManager; this.roleManager = roleManager; this.hostEn = hostEn; } [HttpPost] public async Task<ActionResult<ObjectResult>> Test() {<!-- --> //Check if there is an admin role, if not, add it bool isHas = await this.roleManager.RoleExistsAsync("admin"); if (!isHas) {<!-- --> MyRole myRole = new MyRole(); myRole.Name = "admin"; myRole.NormalizedName = "admin"; myRole.ConcurrencyStamp = "1"; //Add it in, create a new line and add it in IdentityResult result = await this.roleManager.CreateAsync(myRole);//Create role //Return an error if not successful if (!result.Succeeded) {<!-- --> //BadResult is a method in the Controller parent class, and its return value is inherited from ObjectResult // Logically speaking, error information should not be fed back. Log records should be used, and error feedback should be a string. return BadRequest(result.Errors); } } //Check if there is a user pengmingxing, if so, return it, if not, create it, and its role is admin MyUser user = await this.userManager.FindByNameAsync("pengmingxing");//Find the corresponding user based on the user name if (user == null) {<!-- --> MyUser myUser = new MyUser(); myUser.UserName = "pengmingxing"; myUser.Email = "[email protected]"; myUser.EmailConfirmed = true; //Entering password requires email verification myUser.PhoneNumber = "88888888888"; myUser.WeiChart = "88888888"; //The CreateAsync method used here to create a user does not need to set a password, such as mobile phone verification code login. //IdentityResult result = await this.userManager.CreateAsync(myUser);//Create user //If you don't set a password, you can use another one to rewrite it IdentityResult result = await this.userManager.CreateAsync(myUser, "888888");//Create user if (!result.Succeeded) {<!-- --> return BadRequest(result.Errors); } //Bind this user with the admin role result = await this.userManager.AddToRoleAsync(myUser, "admin");//Add the user to the role if (!result.Succeeded) {<!-- --> return BadRequest(result.Errors); } } else {<!-- --> //Determine whether the user is a certain role, if not, create it bool isRole = await userManager.IsInRoleAsync(user, "admin"); if (!isRole) {<!-- --> //Bind this user with the admin role IdentityResult result = await this.userManager.AddToRoleAsync(user, "admin");//Add the user to the role if (!result.Succeeded) {<!-- --> return BadRequest(result.Errors); } } } return Ok(); } [HttpPost] public async Task<ActionResult<string>> CheckUserPassword(string userName, string password) {<!-- --> //Check if there is a corresponding user MyUser user = await userManager.FindByNameAsync(userName); if (user == null) {<!-- --> //Some people will maliciously hack the website, so in a formal environment it is best not to have a username and not give any specific information. if (hostEn.IsDevelopment()) {<!-- --> return BadRequest("Username does not exist"); } else {<!-- --> return BadRequest(); } } //First determine whether the user has been blocked or locked bool isLock = await userManager.IsLockedOutAsync(user); if(isLock) {<!-- --> return BadRequest($"This user has been locked, the lock end time is {user.LockoutEnd}"); } //If the username exists, make sure the password is correct. bool isTrue = await userManager.CheckPasswordAsync(user, password); if(isTrue) {<!-- --> //If the login is successful, delete the recorded login failure data await userManager.ResetAccessFailedCountAsync(user); return Ok("Login successful"); } else {<!-- --> //Record the input incorrect data await userManager.AccessFailedAsync(user); //Get the number of login failures int failNum = await userManager.GetAccessFailedCountAsync(user); //userManager.GetAccessFailedCountAsync(user); This method records the number of errors. If the number of errors exceeds a fixed number, the user will be locked. //You can set it when defining it earlier, you can also use the default value, or you can flexibly use the methods inside to set the lock time. return BadRequest("Incorrect username or password"); } } [HttpPost] public async Task<ActionResult<string>> SendPasswordToken(string userName) {<!-- --> //Find the user MyUser user = await userManager.FindByNameAsync(userName); if (user == null) {<!-- --> if (hostEn.IsDevelopment()) {<!-- --> return BadRequest("Username does not exist"); } return BadRequest(); } //Get Token string token = await userManager.GeneratePasswordResetTokenAsync(user); //The Token will not be sent here, but written directly on the console. /* Notice: Here, when registering, set options.Tokens.PasswordResetTokenProvider to TokenOptions.DefaultEmailProvider, and the returned value is the value. options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider; The result of setting this default value is: 403797 The result of not setting this value: CfDJ8LYtD6oWUe1GqPBiQxTKiPC1v0835ryQmJ8kuK86CKme6Ik6FDhmfY1uAIf01HpxshNC0/Rv4FWbwf3PNE9TYQqtYt1lX8JHwJ1ekAk6b0H6qi6gAViIMxOUFwhy4c5hMc8Fziw 9r2plFzfnEwok3ps8TJaqyH + WVcNp9O4rsBvzBgg9g8dnFgC5qFi/Jvmfeg== The second case below is generally used when a connection needs to be generated. */ Console.WriteLine(token); return Ok(); } /// <summary> /// Obtain the generated Token and then change the password /// I don’t know which table this Token is stored in. It may be stored in the field of the corresponding table data and then encrypted to prevent some people from stealing it. /// Or there is a space inside it that is specially saved for him. /// </summary> /// <param name="myUser"></param> /// <param name="token"></param> /// <param name="newPassword"></param> /// <returns></returns> [HttpPut] public async Task<ActionResult<string>> ResetPasswordByToken(string myUser, string token, string newPassword) {<!-- --> MyUser user = await userManager.FindByNameAsync(myUser); if (user == null) {<!-- --> if (hostEn.IsDevelopment()) {<!-- --> return BadRequest("Username does not exist"); } return BadRequest(); } //Reset username IdentityResult result = await userManager.ResetPasswordAsync(user, token, newPassword); if (result.Succeeded) {<!-- --> //Clear the previous incorrect password records await userManager.ResetAccessFailedCountAsync(user); return Ok("Password changed successfully"); } else {<!-- --> //Record if failed await userManager.AccessFailedAsync(user); return BadRequest("Password modification failed"); } } }