IdentityServer4 + JWT:为“sub”声明定义用户属性
Posted
技术标签:
【中文标题】IdentityServer4 + JWT:为“sub”声明定义用户属性【英文标题】:IdentityServer4 + JWT: Define user property for "sub" claim 【发布时间】:2018-08-27 15:39:03 【问题描述】:我将 IdentityServer4 与 JWT 和 ASP.NET Core 2 一起用于身份验证。由于大查询的性能问题,我还使用IdentityUser<long>
(long
主键而不是string
)作为用户模型。这意味着我在编码的访问令牌内获得了一个有序的用户 ID,默认情况下该用户 ID 将提供给公共。使用 GUID 作为默认类型是有原因的,因此我只想向公众提供 GUID,而不是有序整数。为此,我有一个额外的 GUID 类型的 ExternalID
列,将在创建用户时生成一次。
现在我需要替换 sub
声明,其中默认情况下整数 Id
与我的 ExternalId
(GUID) 一起使用。
我尝试使用ProfileService
和以下GetProfileDataAsync
方法
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var principal = await _claimsFactory.CreateAsync(user);
var claims = principal.Claims.Where(
claim =>
claim.Type != "sub" &&
context.RequestedClaimTypes.Contains(claim.Type))
.ToList();
claims.Add(new Claim("sub", user.ExternalId.ToString()));
context.IssuedClaims = claims;
Startup.cs
services.AddIdentityServer()
// ...
.AddProfileService<ProfileService>();
但这没有任何效果。 IssuedClaims
似乎被忽略了。令牌仍然包含 Id
sub
声明而不是 ExternalId
。
我错过了什么?这是正确的方法吗?
【问题讨论】:
只是为了确定 - 您是否确认您的GetProfileDataAsync()
方法正在被调用?
是的,肯定是在用户第一次登录的时候调用的。
刷新令牌交换没有调用。
我能够通过扩展 Microsoft.AspNetCore.Identity 包中的 UserClaimsPrincipleFactory 来完成这项工作。至少在大多数情况下。当通过首先点击 ResourceOwnerPassword 端点,然后是 UserInfo 端点来获取 id_token 时,我会收到一个禁止的错误。 access_token 的 sub 声明仍显示 int 值,而 id_token 显示 GUID。冲突导致错误。当使用混合流通过 oidc 获取访问令牌时,子声明将正确返回。如果你能解决这个问题,我很想听听!
【参考方案1】:
尽管我在上面发表了评论,但我还是能够完成这项工作,并且没有看到任何副作用。至少我还看不到。您将需要在两个位置替换子声明。
首先像这样扩展 Microsoft.AspNetCore.Identity.UserClaimsPrincipleFactory。这将在收到 Id 令牌之前修改声明。
public sealed class MyUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, ApplicationRole>
public MyUserClaimsPrincipalFactory(UserManager userManager, RoleManager<ApplicationRole> roleManager, IOptions<IdentityOptions> optionsAccessor)
: base(userManager, roleManager, optionsAccessor)
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
var identity = await base.GenerateClaimsAsync(user);
var sub = identity.FindFirst("sub");
if (sub == null)
return identity;
identity.RemoveClaim(sub);
identity.AddClaim(new Claim("sub", user.GlobalUid.ToString()));
return identity;
第二个扩展 IdentityServer4.Services.DefaultClaimsService 像这样。这将解决我在上面的 cmets 中提到的问题。
public class MyClaimsService : DefaultClaimsService
private readonly UserManager _userManager;
public MyClaimsService(IProfileService profile, ILogger<DefaultClaimsService> logger, UserManager userManager) : base(profile, logger)
_userManager = userManager;
protected override IEnumerable<Claim> GetStandardSubjectClaims(ClaimsPrincipal subject)
var sub = subject.GetSubjectId();
var user = _userManager.FindByIdAsync(sub).GetAwaiter().GetResult();
var claims = new List<Claim>
new Claim(JwtClaimTypes.Subject, user?.GlobalUid.ToString() ?? sub),
new Claim(JwtClaimTypes.AuthenticationTime, subject.GetAuthenticationTimeEpoch().ToString(), ClaimValueTypes.Integer),
new Claim(JwtClaimTypes.IdentityProvider, subject.GetIdentityProvider())
;
claims.AddRange(subject.GetAuthenticationMethods());
return claims;
在添加 Microsoft.AspNetCore.Identity 时通过身份生成器将 UserClaimsPrincipleFactory 添加到服务集合中
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedEmail = true;
)
.AddClaimsPrincipalFactory<MyUserClaimsPrincipalFactory>()
然后像这样将 ClaimsService 添加为瞬态
services.AddTransient<IClaimsService, MyClaimsService>();
现在您的所有声明都应作为您为所有令牌提供的属性返回。如果您对此有任何问题,请告诉我,因为它对我来说仍处于测试阶段。
【讨论】:
感谢您的回答!其实我没有时间测试这个。但我稍后会对此进行测试,如果可行,我将标记答案。 没问题。就像我说的,我也没有太多的测试时间,但这似乎有效。让我知道进展如何,或者您是否找到更好的方法! 你有没有得到这个工作?如果您有请投票并标记为正确。谢谢!以上是关于IdentityServer4 + JWT:为“sub”声明定义用户属性的主要内容,如果未能解决你的问题,请参考以下文章
IdentityServer4 - AuthorizeAttribute 不验证 JWT 令牌/自定义用户存储
正确设置 IdentityServer4 Cookie 用于登录,JWT 令牌用于 API 授权
自己发行 JWT 令牌与使用 IdentityServer4(OIDC) 进行 Web API
IdentityServer4实战 - 谈谈 JWT Token 的安全策略