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 的安全策略

未包含在 JWT 中且未发送到 Web Api 的 IdentityServer4 用户声明

IdentityServer4实战 - 谈谈 JWT Token 的安全策略