- A+
IdentityServer4 实现鉴权、授权,AspNetCore Identity实现数据库用户管理表直接生成。
ps:IdentityServer4文档上最后给的例子是 // 配置使用内存存储用户信息,但使用 EF 存储客户端和资源信息,
我初步要实现的是 //数据库存储用户信息 内存存储资源 (下一步资源也放数据库 以后弄好了有机会更)
直接干活:
1.创建.Net6 API程序,一顿引用,包括
防止图片挂掉打一遍文字:
IdentityServer4、
IdengtityServer4.AspNetIdentity、
AspNetCore.Identity.EntityFrameWorkCore(生成数据库表用的)、
EntityFrameWork+Disign+Tool三件套 (缺了不能自动迁移)、
Pomelo.EntityFrameWorkCore.MySql(我是用的MySql,如果是SqlServer 不用这个用一个大概叫EF.Sqlserver的)、
Encrypt (加密MD5用的 不必须)、
下面那个是自带的。
2.建立数据库连接类
1 public class IdpDbContext : IdentityDbContext<ApplicationUser> 2 { 3 public IdpDbContext(DbContextOptions<IdpDbContext> opt) : base(opt) 4 { 5 6 } 7 protected override void OnModelCreating(ModelBuilder builder) 8 { 9 base.OnModelCreating(builder); 10 builder.Entity<ApplicationUser>().ToTable("ApplicationUsers"); 11 #region # 12 //builder.Entity<IdentityUserLogin<string>>().ToTable("ApplicationLogins"); 13 //builder.Entity<IdentityUserClaim<string>>().ToTable("ApplicationUserClaims"); 14 //builder.Entity<ApplicationUserRole>().ToTable("ApplicationUserRoles"); 15 //builder.Entity<IdentityUserToken<string>>().ToTable("ApplicationUserTokens"); 16 //builder.Entity<ApplicationRole>().ToTable("ApplicationRoles"); 17 //builder.Entity<IdentityRoleClaim<string>>().ToTable("ApplicationRoleClaims"); 18 //builder.Entity<ApplicationUserRole>().HasKey(t => t.Id).HasName("PK_UserRole_ID_KEY"); 19 #endregion 20 21 builder.Entity<ApplicationUser>().HasData( 22 new ApplicationUser() 23 { 24 Id = Guid.NewGuid().ToString(), 25 RealName = "alice1", 26 UserName = "alice1", 27 PasswordHash = "alice1" 28 }); 29 #region 初始化用戶与角色的种子数据 30 //1. 更新用戶与角色的外鍵 31 builder.Entity<ApplicationUser>( 32 u => u.HasMany(x => x.UserRoles).WithOne().HasForeignKey(ur => ur.UserId).IsRequired() 33 ); 34 //2. 添加管理员角色 35 var adminRoleId = "f8df1775-e889-46f4-acdd-421ec8d9ba64"; 36 builder.Entity<IdentityRole>().HasData( 37 new IdentityRole() 38 { 39 Id = adminRoleId, 40 Name = "Admin", 41 NormalizedName = "Admin".ToUpper() 42 } 43 ); 44 //3. 添加用户 45 var adminUserId = "f8df1775-e889-46f4-acdd-421ec8d9ba65"; 46 ApplicationUser adminUser = new ApplicationUser 47 { 48 Id = adminUserId, 49 UserName = "admin", 50 NormalizedUserName= "admin".ToUpper(), 51 RealName = "admin", 52 NormalizedEmail = "admin@qq.com".ToUpper(), 53 Email = "admin@qq.com", 54 TwoFactorEnabled = false, 55 EmailConfirmed = true, 56 PhoneNumber = "123456789", 57 PhoneNumberConfirmed = false, 58 59 }; 60 MyPasswordHasher ph = new MyPasswordHasher(); 61 adminUser.PasswordHash = ph.HashPassword(adminUser, "123456"); 62 builder.Entity<ApplicationUser>().HasData(adminUser); 63 //4. 给用户加入管理员角色 64 builder.Entity<IdentityUserRole<string>>().HasData( 65 new IdentityUserRole<string>() 66 { 67 RoleId = adminRoleId, 68 UserId = adminUserId 69 } 70 ); 71 #endregion 72 73 } 74 }
View Code
3.Program里开始加东西(如果是历史的Net版本,是在StartUp里):
直接代码
1 using Microsoft.AspNetCore.Identity; 2 using Microsoft.EntityFrameworkCore; 3 using MyIDP; 4 using MyIDP.Models; 5 using MyIDP.Permission; 6 7 var builder = WebApplication.CreateBuilder(args); 8 9 // Add services to the container. 10 builder.Services.AddControllers(); 11 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 12 builder.Services.AddEndpointsApiExplorer(); 13 builder.Services.AddSwaggerGen(); 14 15 //由此重要 16 builder.Services.AddDbContext<IdpDbContext>(opt => 17 { 18 opt.UseMySql("server=127.0.0.1;Port=3306;database=AccountDb;uid=root;pwd=123456;", new MySqlServerVersion(new Version(8,0,29))); 19 }); 20 21 builder.Services.AddIdentity<ApplicationUser, IdentityRole>() 22 .AddUserManager<MyUserManager>() 23 .AddEntityFrameworkStores<IdpDbContext>() 24 .AddDefaultTokenProviders(); 25 26 builder.Services.AddIdentityServer() 27 .AddDeveloperSigningCredential() 28 29 .AddInMemoryIdentityResources(MyIDP.IdpConfig.GetIdentityResources()) 30 .AddInMemoryClients(MyIDP.IdpConfig.GetClients()) 31 .AddInMemoryApiScopes( MyIDP.IdpConfig.GetScope()) 32 .AddInMemoryApiResources( MyIDP.IdpConfig.GetApiResources()) //.AddResourceOwnerValidator<MyResourceOwnerPasswordValidator>() //这句可以打开自主验证登录用户 33 //.AddProfileService<MyProfileService>() 34 .AddAspNetIdentity<ApplicationUser>() 35 //.AddTestUsers(new List<IdentityServer4.Test.TestUser> 36 //{ 37 // new IdentityServer4.Test.TestUser 38 // { 39 // SubjectId="123", 40 // Username = "alice", 41 // Password = "alice", 42 // Claims = new List<Claim>() { 43 // new Claim(JwtClaimTypes.Role, "superadmin"), 44 // new Claim(JwtClaimTypes.Role, "admin") 45 // } 46 // } 47 //}) 48 ; 49 50 var app = builder.Build(); 51 52 // Configure the HTTP request pipeline. 53 if (app.Environment.IsDevelopment()) 54 { 55 app.UseSwagger(); 56 app.UseSwaggerUI(); 57 } 58 59 app.UseIdentityServer(); 60 app.UseAuthorization(); 61 app.MapControllers(); 62 app.Run();
因为使用的是内存储存t鉴权信息的方式,所以建立IdentityServer4的配置类IdpConfig
1 public static class IdpConfig 2 { 3 public static IEnumerable<IdentityResource> GetIdentityResources() 4 { 5 return new IdentityResource[] 6 { 7 new IdentityResources.OpenId(), 8 new IdentityResources.Profile(), 9 new IdentityResources.Address(), 10 new IdentityResources.Phone(), 11 new IdentityResources.Email() 12 }; 13 } 14 15 public static IEnumerable<ApiResource> GetApiResources() 16 { 17 //return new ApiResource[] 18 //{ 19 // new ApiResource("api1", "My API #1",new List<string>(){JwtClaimTypes.Role}) 20 //}; 21 //新写法 22 return new[] 23 { 24 new ApiResource("api1", "My API #1") 25 { 26 Scopes = { "scope1"} 27 } 28 }; 29 } 30 31 public static IEnumerable<Client> GetClients() 32 { 33 return new[] 34 { 35 #region MyRegion 36 //// client credentials flow client 37 //new Client 38 //{ 39 // ClientId = "console client", 40 // ClientName = "Client Credentials Client", 41 42 // AllowedGrantTypes = GrantTypes.ClientCredentials, 43 44 // ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) }, 45 46 // AllowedScopes = { "api1" } 47 //}, 48 49 #endregion 50 51 // wpf client, password grant 52 new Client 53 { 54 ClientId = "client", 55 AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, 56 ClientSecrets = 57 { 58 new Secret("secret".Sha256()) 59 }, 60 AllowedScopes = //允许当访问的资源 61 { 62 "scope1", 63 //"api1", 64 IdentityServerConstants.StandardScopes.OpenId, 65 IdentityServerConstants.StandardScopes.Email, 66 IdentityServerConstants.StandardScopes.Address, 67 IdentityServerConstants.StandardScopes.Phone, 68 IdentityServerConstants.StandardScopes.Profile } 69 } 70 }; 71 } 72 73 public static IEnumerable<ApiScope> GetScope() 74 { 75 return new ApiScope[] { 76 new ApiScope("scope1"), 77 new ApiScope("scope2"), 78 }; 79 } 80 }
View Code
数据库的usernamager
1 public class MyUserManager : UserManager<ApplicationUser> 2 { 3 public MyUserManager(IUserStore<ApplicationUser> store, IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<ApplicationUser> passwordHasher, 4 IEnumerable<IUserValidator<ApplicationUser>> userValidators, IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger<UserManager<ApplicationUser>> logger) 5 : base(store, optionsAccessor, new MyPasswordHasher(), userValidators, passwordValidators, keyNormalizer, errors, services, logger) 6 { 7 optionsAccessor.Value.Password.RequireDigit = false; 8 optionsAccessor.Value.Password.RequiredLength = 4; 9 optionsAccessor.Value.Password.RequireLowercase = false; 10 optionsAccessor.Value.Password.RequireUppercase = false; 11 optionsAccessor.Value.Password.RequireNonAlphanumeric = false; 12 } 13 14 }
View Code
重写验证密码的方法类MyResourceOwnerPasswordValidator,(如果没有打开Program中的AddResourceOwnerValidator<MyResourceOwnerPasswordValidator>() 则不需要)
public class MyResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { public readonly SignInManager<ApplicationUser> signInManager; private readonly MyUserManager userManager; //public readonly IEventService service; public MyResourceOwnerPasswordValidator(MyUserManager userService, SignInManager<ApplicationUser> signInManager)//, IEventService service) { userManager = userService; this.signInManager = signInManager; //this.service = service; } public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { if (string.IsNullOrEmpty(context.UserName) || string.IsNullOrEmpty(context.Password)) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "验证被拒绝,用户名或者密码为空。"); return; } var user = await userManager.FindByNameAsync(context.UserName); if (user == null) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "验证失败,不存在当前用户。"); return; } //检验用户密码(虽然我也不知道他的密码是采用什么加密方式得到的,但是我也不需要知道) var passwordPass = await userManager.CheckPasswordAsync(user, context.Password); if (!passwordPass) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "验证失败,用户凭证错误"); return; } else { try { await userManager.AddLoginAsync(user, new UserLoginInfo(user.Id, "", user.UserName)); } catch (Exception ex) { ; } finally { context.Result = new GrantValidationResult(user.Id, GrantType.ResourceOwnerPassword, new List<Claim>() { new Claim("account", user.UserName) }); } } return; } }
View Code
MyPasswordHasher
1 public class MyPasswordHasher : PasswordHasher<ApplicationUser> 2 { 3 public override string HashPassword(ApplicationUser user, string password) 4 { 5 //PasswordHasher<ApplicationUser> ph = new PasswordHasher<ApplicationUser>(); 6 //var pstr = ph.HashPassword(new ApplicationUser(), password); 7 //return pstr; 8 return password.MD5(); 9 } 10 11 public override PasswordVerificationResult VerifyHashedPassword(ApplicationUser user, string hashedPassword, string providedPassword) 12 { 13 if (providedPassword.MD5().Equals(hashedPassword)) 14 { 15 return PasswordVerificationResult.Success; 16 } 17 else 18 { 19 return PasswordVerificationResult.Failed; 20 } 21 } 22 }
View Code
创建自己的User类 ApplicationUser继承 IdentityUser 复写自带的AspNetUser表
public class ApplicationUser : IdentityUser { public string MySomething { get; set; } = ""; /// <summary> /// 创建时间 /// </summary> public DateTime CreateTime { get; set; } /// <summary> /// 创建人Id /// </summary> public string CreatorId { get; set; } = ""; /// <summary> /// 否已删除 /// </summary> public bool Deleted { get; set; } /// <summary> /// 姓名 /// </summary> public string RealName { get; set; } /// <summary> /// 性别 /// </summary> public Sex Sex { get; set; } /// <summary> /// 出生日期 /// </summary> public DateTime? Birthday { get; set; } /// <summary> /// 所属部门Id /// </summary> public string DepartmentId { get; set; } = ""; public string OtherData { get; set; } = ""; // 用户角色 用户权限 用户信息 用户登录tokens 重新绑定与父类的关系 命名必须和父类一致 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; } } public enum Sex { [Description("男")] Man = 1, [Description("女")] Woman = 0 }
View Code
至此可以生成数据库迁移后 Postman测试一下