IdentityServer - 如何向客户端凭证令牌添加额外声明?

IdentityServer - How to add additional claim to client credential token?

理想功能:用户登录A网站并通过身份验证。他们单击一个按钮,后端从数据库中查找网站B中的帐户ID,然后将此信息发送到IdentityServer以创建JWT包含 "user_id" 字段。然后用于调用网站 B 上的 REST 端点并进行身份验证,然后 "user_id" 用于创建一个登录 cookie,该 cookie 被发送回网站 A。然后用户被重定向。

我们是 运行 IdentityServer 4,但使用 IdentityServer3 作为我们的主要代码库与它通信是在 .NET Framework 上。我试过在 extras 参数中包含 "user_id" 字段,但这似乎没有任何作用。

var client = new TokenClient(requestPath, CLIENT_ID, CLIENT_SECRET, 
  AuthenticationStyle.PostValues);


var test = new Dictionary<string, string>
{
  { "user_id", "123123" }
};

// request token
var tokenResponse = await client
  .RequestClientCredentialsAsync(apiScope, test)
  .ConfigureAwait(false);

我也尝试过使用 client.RequestCustomAsync 和 client.RequestAsync,但没有成功。

我收到了一个没有问题的令牌,但它不包含 user_id 信息 - 只有普通受众、范围、到期时间等。

对于自定义问题字段,您需要创建继承IProfileService的class,然后实现方法GetProfileDataAsync。看起来像:

public class IdentityProfileService : IProfileService
{
    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var sub = context.Subject.GetSubjectId();
        // Call UserManager or any database to get more fields
        var user = await _userManager.FindByIdAsync(sub);

        // Set returned claims (System.Security.Claims.Claim) by setting context.IssuedClaims
        context.IssuedClaims = claims;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        context.IsActive = true;
    }
}

并且在你注册Identity Service 4时,你需要声明它(示例在.NET Core中,与.NET Framework相同)

var identityBuilder = services.AddIdentityServer().AddProfileService<IdentityProfileService>();

我认为您应该检查 ApiResource 配置。无论您在 ApiResource 配置的 UserClaims 属性 中添加什么声明,这些声明都将出现在访问令牌中。例如

 public IEnumerable<ApiResource> GetApiResources()
 {
      return new List<ApiResource>
      {
            new ApiResource("api1")
            {
                UserClaims = new[] { "CustomClaim1", "CustomClaim2"},
            }, 
       }
 }

在上面的代码中,访问代码将包含CustomClaim1 和CustomClaim2。因此,如果您不提及它们,它们将不会出现在访问令牌中。

以下是对我有用的,主要是following this example

在 IdentityServer 中,创建一个新的 class 实现 IExtensionGrantValidator 的 UserInfoGrant,从请求中提取自定义声明,将它们添加到声明中,然后继续

public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
  var userId = context.Request.Raw[UserIdKey];
  ...
  var claims = new List<Claim>
  {
    new Claim(UserIdKey, userId)
  }
  context.Result = new GrantValidationResult(sub, GrantType, claims);
}

然后我将 class 添加到依赖注入中

builder.AddExtensionGrantValidator<UserInfoGrant>();

我还有一个 class ProfileService,它实现了 IProfileService,它将声明添加到返回的令牌中

public virtual async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
  var authenticationType = context.Subject.Identities.First().AuthenticationType;
  var isCustomAuthenticationType = authenticationType.Equals(CustomGrantName,
    StringComparison.OrdinalIgnoreCase);
  if (isCustomAuthenticationType)
  {
    var claimsToAdd = context.Subject.Identities.First().Claims;
    context.IssuedClaims = claimsToAdd.ToList();
  }
  else { ... }

此配置文件服务也已添加到 DI

builder.AddProfileService<Helpers.ProfileService<TUser>>();

我还向将要使用它的客户端添加了自定义授权类型。

最后,在网站 A 的调用代码中,我请求令牌是这样的:

var tokenResponse = await client.RequestTokenAsync(new TokenRequest {
                    Address = disco.TokenEndpoint,
                    ClientId = CLIENTID,
                    ClientSecret = CLIENTSECRET,
                    GrantType = "custom_grant_name",
                    Parameters = {
                        { "scope", PROTECTED_RESOURCE_NAME },
                        { "user_id", "26616" }
                      }
                    }).ConfigureAwait(false);