在前面我們完成了應用最基礎的功能支援以及資料庫設定,接下來就是我們的使用者角色登入等功能了,在asp.net core中原生Identity可以讓我們快速完成這個功能的開發,在.NET8中,asp.net core identity支援了WebApi的註冊登入。這讓我們在WebApi中可以更爽快的使用。
首先我們需要安裝Microsoft.AspNetCore.Identity.EntityFrameworkCore這個包來建立我們的資料庫結構
在asp.net core identity中預設包含了IdentityUser,IdentityRole,IdentityRoleClaim,IdentityUserClaim,IdentityUserLogin,IdentityUserRole,IdentityUserToken這幾個基礎類別,我們可以直接使用這些,也可以通過繼承來靈活擴充套件我們的表結構。當然,可以按照約定不使用繼承的方式,建立類新增必要的屬性欄位也可。
這裡我們選擇把所有的類都繼承一遍,方便以後擴充套件。
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>
/// 角色型別,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,別的可自行檢視程式碼倉庫。
在WheelDbContext繼承IdentityDbContext,IdentityDbContext支援傳入我們的泛型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");
});
}
}
}
接下來我們使用VS的程式包管理器控制檯。
使用命令建立和執行遷移檔案:
Add-Migration Init
Update-Database
這裡也可以使用Dotnet EF命令:
dotnet ef migrations add Init
dotnet ef database update
在Program中新增下面程式碼:
builder.Services.AddIdentityCore<User>()
.AddRoles<Role>()
.AddEntityFrameworkStores<WheelDbContext>()
.AddApiEndpoints();
這裡指定了Identity使用者型別以及角色型別,並且指定EF操作的DbContext。
AddApiEndpoints則是注入WebAPI所需的服務,我們F12進去可以看到裡面的設定。
/// <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。
使用Post帶上token請求/api/identity/manage/info。成功拿到使用者資訊。
這樣我們就輕輕鬆鬆完成了asp.net core identity對WebApi的整合了。
輪子倉庫地址https://github.com/Wheel-Framework/Wheel
歡迎進群催更。