久違了各位,以前錄製過IdentityServer4的基礎視頻(https://space.bilibili.com/319652230/#/),有興趣瞭解的童鞋能夠看一下,只不過未發表成博客。咱們使用IdentityServer4結和ASP.NET Identity來進行用戶的認證和受權管理,在實際項目中咱們都會繼承ASP.NET Core Identity中IdentityUser類即用戶實體,並添加咱們自定義的擴展屬性,在客戶端(Clients)中咱們只能拿到用戶Id,可是若咱們要獲取用戶中其餘重要的屬性,此時相對於IdentityServer4而言則須要自定義聲明,那麼在IdentityServer4中如何添加自定義聲明並在客戶端中能正確獲取到呢?本文詳細講解一下,對於IdentityServer4我也是初學者,僅僅止於知道和使用而已,如有錯誤的地方,還請大佬指正。html
首先咱們繼承自IdentityUser類並添加額外的屬性好比部門Id,以下:服務器
public class OnLineBookIdentityUser : IdentityUser { public string DepartmentId { get; set; } }
接下來咱們須要將部門Id經過IdentityServer4添加到聲明中,在IdentityServer4中添加自定義聲明咱們須要實現IProfileService接口,該接口有以下兩個方法。async
咱們實現上述兩個方法,在第一個方法中參數也就是用戶基本信息上下文中拿到用戶Id即Subject,而後咱們定義用戶中的部門Id屬性爲idr,並將用戶中的部門Id屬性映射到聲明的idr中,最終實現以下:ide
public class ProfileService : IProfileService { protected UserManager<OnLineBookIdentityUser> _userManager; public ProfileService(UserManager<OnLineBookIdentityUser> userManager) { _userManager = userManager; } public Task GetProfileDataAsync(ProfileDataRequestContext context) { var user = _userManager.GetUserAsync(context.Subject).Result; var claims = new List<Claim> { new Claim("idr", user.DepartmentId), }; context.IssuedClaims.AddRange(claims); return Task.FromResult(0); } public Task IsActiveAsync(IsActiveContext context) { var user = _userManager.GetUserAsync(context.Subject).Result; context.IsActive = true; return Task.FromResult(0); } }
接下來在注入IdentityServer4時,添加咱們自定義實現的ProfileService,更多基礎請參考IdentityServer4官網以及我所錄製的IdentityServer4基礎視頻。ui
//注入IdentityServer4使用AspNetIdentity services.AddIdentityServer(options => { options.Authentication.CookieLifetime = TimeSpan.FromMinutes(1); }) .AddDeveloperSigningCredential() .AddAspNetIdentity<OnLineBookIdentityUser>() .... .AddProfileService<ProfileService>();
曉晨姐姐在他發表的博客文章(http://www.cnblogs.com/stulzq/p/8726002.html)中說必須還要實現IResourceOwnerPasswordValidator接口,那麼在客戶端獲取到自定義聲明應該是經過調用接口的方式獲取用戶自定義聲明(不知是否理解正確或者說經過客戶端中User中Principal獲取到呢?)這裏咱們並未實現上述IResourceOwnerPasswordValidator接口,咱們看看在客戶端是否能拿到上述咱們聲明的idr呢?在客戶端咱們定義以下控制器,訪問須要進行受權,並獲取咱們添加的自定義聲明idr,以下:this
[Route("[controller]"), Authorize] public class OrderController : Controller { private readonly IOrderService _orderService; public OrderController(IOrderService orderService) { _orderService = orderService; } [HttpGet("index")] public IActionResult Index() { var idr = User.FindFirst("idr")?.Value; return View(nameof(Index), idr); } }
並在上述index視圖中咱們答應自定義聲明idr所對應的部門的值,以下:spa
@model string @if (Model is null) { <h1>idr is null</h1> } else { <h1>@Model.ToString()</h1> }
接下來咱們經過動態gif來演示下,注意以下視頻http://localhost:5000/爲IdentityServer4認證、受權服務器端。而http://localhost:5003/爲客戶端。code
上述咱們能夠看到在登陸以後重定向到客戶端,咱們拿到ack即AccessToken裏面有自定義聲明idr,可是當咱們訪問Order/Index並未獲取到idr,這是爲什麼,這是由於咱們經過以下即ClaimPrincipal去獲取idr時,其實是獲取的id_token裏面的用戶信息,而不是AccessToken,而id_token咱們看到沒有idr,因此纔出現沒有獲取到的狀況。視頻
var idr = User.FindFirst("idr")?.Value;
爲了解決這個問題,咱們能夠在經過IdentityServer4建立的Clients表中所對應的調用客戶端中的以下列設置爲True便可。htm
接下來咱們再來演示一下,此時咱們將看到解析經過id_token將返回idr,並能在客戶端讀取到idr。
除了上述方式經過實現ProfileService接口外,咱們還能夠經過實現自定義UserClaimsPrincipalFactory工廠類來實現,複寫CreateAsync方法來建立自定義聲明以下:
public class CustomizeUserClaimsFactory<TRole> : UserClaimsPrincipalFactory<OnLineBookIdentityUser, TRole> where TRole : class { public CustomizeUserClaimsFactory(UserManager<OnLineBookIdentityUser> userManager, RoleManager<TRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor) { } public async override Task<ClaimsPrincipal> CreateAsync(OnLineBookIdentityUser user) { var cliamsPrincipal = await base.CreateAsync(user); var identity = cliamsPrincipal.Identities.First(); if (!identity.HasClaim(x => x.Type == "idr")) { identity.AddClaim(new Claim("idr", user.DepartmentId)); } return cliamsPrincipal; } }
而後經過建立擴展方法將上述自定義用戶聲明工廠進行注入,以下:
public static IdentityBuilder AddCustomizeUserClaimsPrincipalFactory(this IdentityBuilder builder) { var interfaceType = typeof(IUserClaimsPrincipalFactory<>); interfaceType = interfaceType.MakeGenericType(builder.UserType); var classType = typeof(CustomizeUserClaimsFactory<>); classType = classType.MakeGenericType(builder.RoleType); builder.Services.AddScoped(interfaceType, classType); return builder; }
最後在注入Identity時,添加上述自定義聲明工廠,以下:
services.AddIdentity<OnLineBookIdentityUser, IdentityRole>() .AddEntityFrameworkStores<OnLineBookDbContext>() .AddDefaultTokenProviders() .AddCustomizeUserClaimsPrincipalFactory();
本節內容須要有必定IdentityServer4基礎,如若不太瞭解請參考官方文檔,同時針對如上在客戶端如何獲取自定義聲明,重點在於在對應客戶端表中設置AlwaysIncludeInIdToken爲True纔好使,並未去深究設置該列爲True所產生的反作用,感謝閱讀,如有不一樣看法,望留下您的評論。