为什么我在 IdentityServer4 的同意屏幕上看到重复的范围?

Why am I seeing duplicate Scopes on IdentityServer4's consent screen?

我正在编写一个 IdentityServer4 实现并使用 here 中描述的 Quickstart 项目。

当您定义一个 ApiResource(现在使用 InMemory 类)时,IdentityServer 会创建一个与资源同名的范围。例如

public static IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        new ApiResource("api", "My API")
    };
}

将创建一个名为 "api" 的范围(这是在 ApiResource 构造函数中完成的)。如果我在我的客户端对象上添加 "api" 作为允许的范围(使用 InMemoryClients 进行概念验证)并在我的 [=49= 的身份验证请求中的范围查询字符串参数中请求此 api 范围] 客户端我收到一条 invalid_scope 错误消息。

我通过关注 this documentation 发现您可以通过作用域 属性 将作用域添加到 ApiResource,就像这样

new ApiResource
{
     Name = "api",
     DisplayName = "Custom API",
     Scopes = new List<Scope>
     {
          new Scope("api.read"),
          new Scope("api.write")
      }
 }

所以现在,如果我改为像这样定义我的 ApiResource 并请求范围 api.read 和 api.write(并将它们添加到客户端对象上的 AllowedScopes 属性),那么一切正常很好,除了显示重复范围的同意页面。它显示 api.read 2 次和 api.write 2 次。在此处查看同意屏幕

客户端配置如下:

new Client
{
     ClientId = "client.implicit",
     ClientName = "JavaScript Client",
     AllowedGrantTypes = GrantTypes.Implicit,
     AllowAccessTokensViaBrowser = true,
     RedirectUris = { "http://localhost:3000/health-check" },
     PostLogoutRedirectUris = { "http://localhost:3000" },
     AllowedCorsOrigins =     { "http://localhost:3000" },
     AllowedScopes = {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "customApi.read", "customApi.write"
                     }
}

为什么会这样?我做错了什么吗?

更新: 此处显示范围的发现文档的一部分仅列出一次...

看起来问题出在快速入门 UI... 或 Scope.cs class 上,具体取决于您如何看待它。具体来说,在 class ConsentService.cs

所示的方法和行中

以下代码

vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();

没有过滤掉重复项。也就是说,即使两个作用域具有相同的名称,它们也不被视为相等。因此,如果 GetHashCodeEqualsScope.cs 中被覆盖(在 IdentityServer4 中——而不是快速入门),那么它会解决这个问题。在那种情况下,SelectMany 将 return 一个唯一的集合。这是因为 ApiResources 属性 是作为 HashSet 实现的。或者,您可以编写自己的逻辑,使 return 成为一组独特的范围。这就是我解决问题的方法。我在 this post 中写了一些与 Jon Skeet 的回答非常相似的内容,过滤掉了重复的范围。

有一个刚刚在 1.5 中修复的错误解决了这个问题:https://github.com/IdentityServer/IdentityServer4/pull/1030。请升级并查看是否可以为您解决问题。谢谢

问题出在 InMemoryResourcesStore.FindApiResourcesByScopeAsync 实现中的 IdentityService4 代码中,已通过此 commit 修复。您可以使用自 2017 年 6 月 22 日以来包含它的 dev 分支,但它从未在任何针对 .NET Standard 1.4 的 NuGET 包中发布,这非常烦人。

我创建了一个问题并请求对其进行修补: https://github.com/IdentityServer/IdentityServer4/issues/1470

为了修复视图,我将标有 Todo 的行添加到 ConsentService.cs

var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any()))
{
    // TODO: Hotfix to cleanup scope duplication:
    resources.ApiResources = resources.ApiResources.DistinctBy(p => p.Name).ToList();
    return CreateConsentViewModel(model, returnUrl, request, client, resources);
}

这解决了显示问题,但范围仍将多次包含在访问令牌中,这使其更大,因为它使 API 的范围计数平方。我有 3 个作用域,所以每个作用域被包含 3 次,添加了 6 个不需要的作用域副本。但至少它在修复之前是可用的。