IdentityServer4 + JWT:为“子”声明定义用户属性

时间:2018-03-19 06:16:47

标签: c# asp.net-core jwt identityserver4

我正在使用带有JWT和ASP.NET Core 2的IdentityServer4进行身份验证。由于大查询的性能问题,我还使用IdentityUser<long>long主键而不是string)作为用户模型。这就是我在编码的访问令牌中获得了一个默认为public的有序用户ID。使用GUID作为默认类型有它的原因,因此我想要公开只给出GUID,而不是有序整数。为此,我有一个GUID类型的额外ExternalID列,在创建用户时将生成一次。

现在我需要替换sub声明,默认情况下整数IdExternalId(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

我错过了什么?这是正确的做法吗?

1 个答案:

答案 0 :(得分:0)

尽管上面有我的评论,但我仍然能够做到这一点,并且没有副作用。至少我看不到。您将需要在两个位置替换子声明。

首先像这样扩展Microsoft.AspNetCore.Identity.UserClaimsPrincipleFactory。这将在收到ID令牌之前修改声明。

CREATE FUNCTION [dbo].[GetItemMTDIssues]
    (@fac char(20), @partno char(25), @rev char(3), @currentdate datetime)
    returns numeric (15,5)
    as

    begin
        declare @returnval as numeric (15,5)

        set @returnval =
        isnull(
        (select sum(fQty) 
                from intran 
                where ftype = 'I'
                and month(fdate) = month(@currentdate)
                and year(fdate) = year(@currentdate)
                and fac = @fac
                and fpartno = @partno
                and fcpartrev = @rev)
        ,0.0) * -1

        return @returnval
    end

第二个扩展IdentityServer4.Services.DefaultClaimsService像这样。这将解决我在上面的评论中提到的问题。

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;
    }
}

添加Microsoft.AspNetCore.Identity时,通过身份生成器将UserClaimsPrincipleFactory添加到服务集合中

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;
    }
}

然后将ClaimsService添加为类似这样的过渡

 services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
     {
         options.User.RequireUniqueEmail = true;
         options.SignIn.RequireConfirmedEmail = true;
     })
     .AddClaimsPrincipalFactory<MyUserClaimsPrincipalFactory>() 

现在,您所有的债权应作为您为所有令牌提供的属性返回。如果您对此有任何疑问,请告诉我,因为它仍在测试阶段。