Net6 EfCore + Identity權限框架入門

十年河東,十年河西,莫欺少年窮

學無止境,精益求精

微軟的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

    /// <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

 

 6、測試代碼

需要說明的是,微軟Identity框架會生成用戶表【T_Users】、角色表【T_Roles】、用戶角色關聯表【AspNetUserRoles】、及其他表

其他的一些表,我沒做研究,本篇博客僅供入門

 

 

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、模擬找回密碼,發送驗證碼到郵箱【代碼只生產驗證碼】

        /// <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 參數

 add-migration initRole  -context AuthDbContext

 update-database -context AuthDbContext

實際開發項目中,我們上線後的項目,每次改動數據庫時,也可以採用新建DbContext的辦法進行,這樣在更新線上數據庫時,我們只需指定 -context參數就能更新新增加的數據表,這樣做的好處是不會影響到已上線的數據庫、表

@天才owl的博客

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章