namespace Wheel.Domain.Identity { public class User : IdentityUser, IEntity<string> { public virtual DateTimeOffset CreationTime { get; set; } public virtual ICollection<UserClaim> Claims { get; set; } public virtual ICollection<UserLogin> Logins { get; set; } public virtual ICollection<UserToken> Tokens { get; set; } public virtual ICollection<UserRole>? UserRoles { get; set; } } } namespace Wheel.Domain.Identity { public class Role : IdentityRole, IEntity<string> { /// <summary> /// 堆代码 duidaima.com /// 角色类型,0管理台角色,1客户端角色 /// </summary> public RoleType RoleType { get; set; } public Role(string roleName, RoleType roleType) : base (roleName) { RoleType = roleType; } public Role(string roleName) : base (roleName) { } public Role() : base () { } public virtual ICollection<UserRole> UserRoles { get; set; } public virtual ICollection<RoleClaim> RoleClaims { get; set; } } }这里主要展示一下User和Role,别的可自行查看代码仓库。
namespace Wheel.EntityFrameworkCore { public class WheelDbContext : IdentityDbContext<User, Role, string, UserClaim, UserRole, UserLogin, RoleClaim, UserToken> { private StoreOptions? GetStoreOptions() => this.GetService<IDbContextOptions>() .Extensions.OfType<CoreOptionsExtension>() .FirstOrDefault()?.ApplicationServiceProvider ?.GetService<IOptions<IdentityOptions>>() ?.Value?.Stores; public WheelDbContext(DbContextOptions<WheelDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); ConfigureIdentity(builder); } void ConfigureIdentity(ModelBuilder builder) { var storeOptions = GetStoreOptions(); var maxKeyLength = storeOptions?.MaxLengthForKeys ?? 0; builder.Entity<User>(b => { b.HasKey(u => u.Id); b.HasIndex(u => u.NormalizedUserName).HasDatabaseName("UserNameIndex").IsUnique(); b.HasIndex(u => u.NormalizedEmail).HasDatabaseName("EmailIndex"); b.ToTable("Users"); b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken(); b.Property(u => u.Id).HasMaxLength(36); b.Property(u => u.UserName).HasMaxLength(256); b.Property(u => u.NormalizedUserName).HasMaxLength(256); b.Property(u => u.Email).HasMaxLength(256); b.Property(u => u.NormalizedEmail).HasMaxLength(256); b.Property(u => u.CreationTime).HasDefaultValue(DateTimeOffset.Now); b.HasMany(e => e.Claims) .WithOne(e => e.User) .HasForeignKey(uc => uc.UserId) .IsRequired(); b.HasMany(e => e.Logins) .WithOne(e => e.User) .HasForeignKey(ul => ul.UserId) .IsRequired(); b.HasMany(e => e.Tokens) .WithOne(e => e.User) .HasForeignKey(ut => ut.UserId) .IsRequired(); b.HasMany(e => e.UserRoles) .WithOne(e => e.User) .HasForeignKey(ur => ur.UserId) .IsRequired(); }); builder.Entity<UserClaim>(b => { b.HasKey(uc => uc.Id); b.ToTable("UserClaims"); }); builder.Entity<UserLogin>(b => { b.HasKey(l => new { l.LoginProvider, l.ProviderKey }); if (maxKeyLength > 0) { b.Property(l => l.LoginProvider).HasMaxLength(maxKeyLength); b.Property(l => l.ProviderKey).HasMaxLength(maxKeyLength); } b.ToTable("UserLogins"); }); builder.Entity<UserToken>(b => { b.HasKey(t => new { t.UserId, t.LoginProvider, t.Name }); if (maxKeyLength > 0) { b.Property(t => t.LoginProvider).HasMaxLength(maxKeyLength); b.Property(t => t.Name).HasMaxLength(maxKeyLength); } b.ToTable("UserTokens"); }); builder.Entity<Role>(b => { b.HasKey(r => r.Id); b.HasIndex(r => r.NormalizedName).HasDatabaseName("RoleNameIndex").IsUnique(); b.ToTable("Roles"); b.Property(u => u.Id).HasMaxLength(36); b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken(); b.Property(u => u.Name).HasMaxLength(256); b.Property(u => u.NormalizedName).HasMaxLength(256); b.HasMany(e => e.UserRoles) .WithOne(e => e.Role) .HasForeignKey(ur => ur.RoleId) .IsRequired(); b.HasMany(e => e.RoleClaims) .WithOne(e => e.Role) .HasForeignKey(rc => rc.RoleId) .IsRequired(); }); builder.Entity<RoleClaim>(b => { b.HasKey(rc => rc.Id); b.ToTable("RoleClaims"); }); builder.Entity<UserRole>(b => { b.HasKey(r => new { r.UserId, r.RoleId }); b.ToTable("UserRoles"); }); } } }执行数据库迁移命令
Add-Migration Init Update-Database这里也可以使用Dotnet EF命令:
dotnet ef migrations add Init dotnet ef database update执行完命令后我们连接数据库即可看到表成功创建。
builder.Services.AddIdentityCore<User>() .AddRoles<Role>() .AddEntityFrameworkStores<WheelDbContext>() .AddApiEndpoints();这里指定了Identity用户类型以及角色类型,并且指定EF操作的DbContext。
/// <summary> /// Adds configuration and services needed to support <see cref="IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi{TUser}(IEndpointRouteBuilder)"/> /// but does not configure authentication. Call <see cref="BearerTokenExtensions.AddBearerToken(AuthenticationBuilder, Action{BearerTokenOptions}?)"/> and/or /// <see cref="IdentityCookieAuthenticationBuilderExtensions.AddIdentityCookies(AuthenticationBuilder)"/> to configure authentication separately. /// </summary> /// <param name="builder">The <see cref="IdentityBuilder"/>.</param> /// <returns>The <see cref="IdentityBuilder"/>.</returns> public static IdentityBuilder AddApiEndpoints(this IdentityBuilder builder) { ArgumentNullException.ThrowIfNull(builder); builder.AddSignInManager(); builder.AddDefaultTokenProviders(); builder.Services.TryAddTransient<IEmailSender, NoOpEmailSender>(); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<JsonOptions>, IdentityEndpointsJsonOptionsSetup>()); return builder; }接下来就是配置API了,在中间件中添加MapIdentityApi:
app.MapGroup("api/identity") .WithTags("Identity") .MapIdentityApi<User>();这里需要注意的是,如果不先MapGroup,则我们的请求路径只直接从/开始的,MapGroup("api/identity")则是指定从/api/identity开始。WithTags则是指定我们Swagger生成API的Tag显示名称。
直接调用register和login方法即可完成注册登录,这里只贴上一个登录返回的截图,可以看到我们成功拿到了accessToken以及refreshToken。