【问题标题】:IdentityServer4 claims are not part of the token on hybrid flowIdentityServer4 声明不是混合流令牌的一部分
【发布时间】:2019-07-21 11:40:02
【问题描述】:

我正在尝试构建一个混合流,并使用 IdentityServer4 对返回的访问令牌进行声明。我正在使用QuickStart UI 控件。

在用户成功通过身份验证后,在我的AccountController 中,我有以下代码可以让他登录:

await HttpContext.SignInAsync("anon@nymous.com", "anon@nymous.com", null, new Claim("MyName", "Ophir"));

在导致此流程的 MVC 网站中,在我要“保护”的页面上,我有以下代码:

[Authorize]
public IActionResult RestrictedMvcResource()
{
       var token = _httpContext.HttpContext.GetTokenAsync("access_token").Result;
       var identity = User.Identity;
       return View();
}

成功登录后,调试器会很好地命中此代码,并且我正在获取访问令牌。

问题是,如果我解码我的访问令牌(我正在使用 https://jwt.io/),我会看到名称和主题,但我没有看到我定义的 MyName 声明。

(我的系统中有另一个 client_credentials 流,它确实返回了对令牌的声明 - 但它使用不同的代码流)。

如何返回混合流的令牌声明?

编辑:

解决这个问题需要两件事的结合:

  1. 按照(选定)答案中的建议实施IProfileService。这是我的实现:
public class ProfileService : IProfileService
{
    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        context.AddRequestedClaims(context.Subject.Claims);

        foreach (Claim claim in context.Subject.Claims)
        {
            if (context.IssuedClaims.Contains(claim))
                continue;

            context.IssuedClaims.Add(claim);
        }

        return Task.FromResult(0);
    }

    public Task IsActiveAsync(IsActiveContext context)
    {
        context.IsActive = true;
        return Task.FromResult(0);
    }
}

这将添加任何尚未在令牌上的声明。

  1. 调用HttpContext.SignInAsync 时,您必须传递声明列表,否则context.Subject.Claims 集合中将没有其他声明。

【问题讨论】:

  • 如果为 client_credentials 添加了声明,那么这意味着您在 ClientClaims 表中配置了声明?
  • 为什么声明应该是 access_token 的一部分?似乎更多的声明应该是 identity_token 的一部分,尽管您可能不会在那里找到它,因为默认情况下它只包含子声明,除非 AlwaysIncludeUserClaimsInIdToken 为真:var token = await _httpContext.HttpContext.GetTokenAsync("identity_token");。否则从 UserInfo 端点获取声明(使用访问令牌)。
  • 声明不是访问令牌的一部分。我提到过,在一种类型的流程中,声明成功地在令牌上进行,而在另一种类型的流程中则没有。
  • 为什么要在代码中向 context.Subject.Claims 集合添加声明?更灵活的解决方案是将声明保留在 UserClaims 表中。
  • @RuardvanElburg 我的解决方案不使用文档中的 EF 持久性,我编写了一个自定义解决方案包装 IdentityServer..

标签: c# asp.net-identity identityserver4


【解决方案1】:

如果您想向令牌添加自定义声明,可以实现自定义 IProfileService

您可以在Identity Server 4 docs找到更多信息。

简单的自定义配置文件服务示例如下:

public class CustomProfileService : IProfileService
{
    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        context.AddRequestedClaims(context.Subject.Claims);
        context.IssuedClaims.Add(new Claim("MyName", "Ophir"));
        return Task.FromResult(0);
    }

    public Task IsActiveAsync(IsActiveContext context)
    {
        context.IsActive = true;    
        return Task.FromResult(0);
    }
}

一旦你有了这个,只需将它注册到 DI:

services.AddTransient<IProfileService, CustomProfileService>();

每当请求access_tokenid_token 时都会调用它。如果您只想要特定类型的令牌中的额外声明,则需要根据 Ruard 的评论检查 context.Caller

编辑: 或者,您可以按照Identity Server 4 quickstarts 之一中的示例将声明直接添加到用户配置中:

            new TestUser
            {
                SubjectId = "1",
                Username = "alice",
                Password = "password",

                Claims = new []
                {
                    new Claim("MyName", "Ophir")
                }
            },

如果您最终没有实现自定义 IProfileService 并继续使用 DefaultProfileService,那么您还需要在配置中添加自定义 IdentityResource

return new List<IdentityResource>
{
    //..Your other configured identity resources

    new IdentityResource(
    name: "custom.name",
    displayName: "Custom Name",
    claimTypes: new[] { "MyName" });
};

任何希望在令牌中添加此声明的客户都需要请求custom.name 范围。

【讨论】:

  • 没有需要来实现IProfileService。这只能通过配置来完成。此外,上面的代码实际上会阻止添加配置的声明,因为缺少context.AddRequestedClaims(context.Subject.Claims); 行。在不检查 Context.Caller 的情况下,此代码会将声明添加到访问令牌和身份令牌。
  • @RuardvanElburg 是的,感谢您指出这些,我忘记了context.AddRequestedClaims() 在他们的DefaultProfileService 中,所以这种行为会丢失。对配置也赞不绝口,我从未亲自在配置中使用过用户声明,我假设如果请求 profile 范围,它们将被添加。
  • 如果我没记错的话,默认情况下id_token中只返回sub。应调用 UserInfo 端点以获取其他声明。为防止此额外调用,当同时请求 id_token 和 access_token 时,请将 AlwaysIncludeUserClaimsInIdToken 设置为 true。另请注意,默认配置文件将不包含声明 MyName。将 MyName 作为 IdentityClaim 添加到配置文件(或添加一个包含 MyName 作为 IdentityClaim 的新 IdentityResource,并确保客户端请求范围)。
  • @RuardvanElburg 是的,你没有弄错任何 cmets :),我刚刚在本地尝试过。
  • @RuardvanElburg 如何在不实施 IProfileService 的情况下通过配置来完成?有什么例子吗?
【解决方案2】:

我可以通过两个步骤向身份服务器的令牌添加声明。

通过像其他答案之一所示的自定义配置文件服务,您可以为用户定义自己的声明。

然后可以通过 userinfo 端点请求这些声明。

或者您创建一个名为 IncludeNameInAccessToken 的 Api(资源),如果您将该 Api 作为范围请求,则默认情况下会将名称声明添加到访问令牌中。

【讨论】:

    【解决方案3】:

    AspNet.Security.OpenIdConnect.Server 不会序列化未设置目标的声明。我在使用 OpenIdDict 时遇到了这个问题。

    试试这个:

    var claim = new Claim("MyName", "Ophir");
    claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken);
    
    await HttpContext.SignInAsync("anon@nymous.com", "anon@nymous.com", null, claim);
    

    您可能需要添加这些命名空间:

    using AspNet.Security.OpenIdConnect.Extensions;
    using AspNet.Security.OpenIdConnect.Primitives;
    

    【讨论】:

    • 我使用的是 .NET Core 2.2。这些命名空间似乎无效,SetDestinationsOpenIdConnectConstants 无法识别。
    • 我已经安装了以下 nuget:nuget.org/packages/AspNet.Security.OpenIdConnect.Server 不幸的是它没有帮助。我的代币仍然没有任何声明。
    猜你喜欢
    • 2020-02-14
    • 2021-06-11
    • 2019-03-21
    • 1970-01-01
    • 2021-12-27
    • 1970-01-01
    • 2015-07-07
    • 2017-08-20
    • 1970-01-01
    相关资源
    最近更新 更多