您如何在 Identity Server 4.0.3 中正确授予给定客户端对 ApiResource 的访问权限?

How do you properly grant access to an ApiResource for a given client in Identity Server 4.0.3?

以前我们可以定义以下配置,并且它可以工作:

public static IEnumerable<ApiScope> GetApiScopes() =>
    new List<ApiScope>
    {
        new ApiScope(
            name: "Scope1",
            displayName: "scope1 description",
            userClaims: new[] { "claim1" }),
        new ApiScope(
            name: "Scope2",
            displayName: "scope2 description",
            userClaims: new[] { "claim2", "claim3", "claim4"}),
        new ApiScope(
            name: "Scope3",
            displayName: "scope3 description",
            userClaims: new[] { "claim5" }),
        new ApiScope(
            name: "Scope4",
            displayName: "scope4 description",
            userClaims: new[] { "claim6" })
    };
    
public static IEnumerable<ApiResource> GetApiResources() =>
    new List<ApiResource>
    {
        new ApiResource("MyApi", "MyApi description")
        {
            ApiSecrets = { new Secret("secret").Sha256() },
            Scopes =
            {
                "Scope1",
                "Scope2",
                "Scope3",
                "Scope4"
            }
        }
    };
    
public static IEnumerable<Client> GetClients() =>
    new List<Client>
    {
        new Client
        {
            Enabled = true,
            ClientId = "client",
            ClientSecrets = "secret"
            AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
            AllowOfflineAccess = true,
            AccessTokenType = AccessTokenType.Reference,
            RequireConsent = false,
            RequirePkce = false,
            UpdateAccessTokenClaimsOnRefresh = true,
            RefreshTokenExpiration = TokenExpiration.Absolute,
            AbsoluteRefreshTokenLifetime = 123456,
            RefreshTokenUsage = TokenUsage.ReUse,
            AccessTokenLifetime = 600000,
            AllowedScopes = { "MyApi" }, // This previously worked, now it doesn't
        }
    };

但是由于各种变化,有点解释here,你不能再做上面的事情了,因为写在Client.AllowedScopes中的"MyApi"不是作用域——换句话说你可以'像以前一样通过提供资源名称请求访问资源

相反,要使上述内容在 Identity Server 4.0.3 中运行,您必须执行以下 hack,我认为这是非常错误的,因此问题:

public static IEnumerable<ApiScope> GetApiScopes() =>
    new List<ApiScope>
    {
        new ApiScope(
            name: "Scope1",
            displayName: "scope1 description",
            userClaims: new[] { "claim1" }),
        new ApiScope(
            name: "Scope2",
            displayName: "scope2 description",
            userClaims: new[] { "claim2", "claim3", "claim4"}),
        new ApiScope(
            name: "Scope3",
            displayName: "scope3 description",
            userClaims: new[] { "claim5" }),
        new ApiScope(
            name: "Scope4",
            displayName: "scope4 description",
            userClaims: new[] { "claim6" }),
        // Wrapper
        new ApiScope(
            name: "MyApi",
            displayName: "",
            // Manually add all claims from above scopes. 
                        // If you end up in the future changing one of the above scopes's required claims,
                        // well, make sure you do the same here...
            userClaims: new[] { "claim1", "claim2", "claim3", "claim4", "claim5", "claim6"})        
    };
    
public static IEnumerable<ApiResource> GetApiResources() =>
    new List<ApiResource>
    {
        new ApiResource("MyApi", "MyApi description")
        {
            ApiSecrets = { new Secret("secret").Sha256() },
            Scopes =
            {
                "MyApi"
            }
        }
    };
    
public static IEnumerable<Client> GetClients() =>
    new List<Client>
    {
        new Client
        {
            Enabled = true,
            ClientId = "client",
            ClientSecrets = "secret"
            AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
            AllowOfflineAccess = true,
            AccessTokenType = AccessTokenType.Reference,
            RequireConsent = false,
            RequirePkce = false,
            UpdateAccessTokenClaimsOnRefresh = true,
            RefreshTokenExpiration = TokenExpiration.Absolute,
            AbsoluteRefreshTokenLifetime = 123456,
            RefreshTokenUsage = TokenUsage.ReUse,
            AccessTokenLifetime = 600000,
                        // now works because we have a fake "MyApi" scope,
                        // encapsulating our previously well-defined structure of scopes
            AllowedScopes = { "MyApi" }, 
        }
    };

将整个 ApiResource 的范围包装成一个范围并定义所述范围中存在的所有声明是零意义的。

有人可以阐明这一点 - 实现我们在过去版本的 Identity Server4 中实现的目标的正确方法是什么?

编辑: 基本上我想问的是 - 你如何要求 将特定资源授予特定资源集范围? (如果它们中的任何一个不存在于令牌中 - 使其无效)

之前之所以有效,是因为在以前的版本中,资源定义自动包含一个同名范围。

请注意,MyApi在这里是作用域名称,等于资源名称。

AllowedScopes = { "MyApi" }

但是令牌中包含资源名称作为受众,这很令人困惑。因此,如果客户端至少有一个属于资源一部分的作用域,则它可以访问该资源。在资源中,应验证范围以确保客户端有权使用资源的特定部分,例如:

services
    .AddAuthorization(options =>
    {
        options.AddPolicy("Scope1", p => p.RequireClaim("scope", "Scope1"));

看来在您的情况下,无论指定的范围如何,客户端都可以访问整个资源。

所以您应该做的是,验证资源中的范围并在客户端定义中命名允许的范围:

AllowedScopes = { "Scope1", "Scope2", "Scope3", "Scope4" }

如果允许所有范围,则省略该行。这将自动包括所有允许的范围。


根据评论更新。

您可以在 api 中验证接收不记名令牌的范围,方法如下:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddIdentityServerAuthentication(options =>
    {
        options.JwtBearerEvents = new JwtBearerEvents
        {
            OnTokenValidated = async context =>
            {
                // To verify if required scopes are included:
                var requiredScopes = new List<string> { "scope1" , "scope2", "scope3", "scope4" };
                var foundScopes = context.Principal.Claims.Count(c => c.Type == "scope" && allowedScopes.Contains(c.Value));

                if (foundScopes != requiredScopes.Count)
                {
                    context.Fail("Invalid number of scopes");
                }
            }
        };
    });