久违了各位,以前录制过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所产生的反作用,感谢阅读,如有不一样看法,望留下您的评论。