c# - IdentityServer4 Introspection Endpoint API 使用无效的散列算法

标签 c# identityserver4

尝试使用 IdentityServer4 上的 Introspection Endpoint 验证 token 。我不断收到 401:未经授权。我的日志看起来像这样:

dbug: IdentityServer4.EntityFramework.Stores.ResourceStore[0]
      Found MyAPI API resource in database
info: IdentityServer4.Validation.HashedSharedSecretValidator[0]
      Secret: MyAPI API uses invalid hashing algorithm.
dbug: IdentityServer4.Validation.SecretValidator[0]
      Secret validators could not validate secret
fail: IdentityServer4.Validation.ApiSecretValidator[0]
      API validation failed.
fail: IdentityServer4.Endpoints.IntrospectionEndpoint[0]
      API unauthorized to call introspection endpoint. aborting.

我的 API 是这样配置的:

new ApiResource
                {
                    Name = "MyAPI",
                    DisplayName = "My API",
                    ApiSecrets =
                    {
                        new Secret("TopSecret".Sha256())
                    },
                }

我将内容类型的 header 作为 application/x-www-form-urlencoded 传递,并将授权作为 Basic xxxxxxxxxxxxxxxxx 传递,其中 x 是我的 base64 编码的身份验证字符串 (myapi:TopSecret)。我的 token 在帖子正文中

我错过了什么?为什么我收到“MyAPI API 使用无效的散列算法”?如果无效,什么是有效的哈希算法?

附加信息:我的资源包含在通过 Entity Framework 访问的 SQL 数据库中。具体来说,设置与找到的快速入门文档中的设置相同 here .为了达到我现在的目的,我必须手动将我的 API 添加到 ApiSecrets 表并为其指定一个类型 (SharedSecret) 和一个值,即 Sha256 密码。

在 Startup.cs 中,我的 COnfigureServices 包括

services.AddIdentityServer()
            .AddTemporarySigningCredential()
            .AddInMemoryApiResources(Configurations.Scopes.GetApiResources())
            .AddInMemoryClients(Configurations.Clients.GetClients())

            .AddConfigurationStore(builder =>
                builder.UseSqlServer(connectionString, options =>
                    options.MigrationsAssembly(migrationsAssembly)))
            .AddOperationalStore(builder =>
                builder.UseSqlServer(connectionString, options =>
                    options.MigrationsAssembly(migrationsAssembly)));

        // include the password validation routine
        services.AddTransient<IResourceOwnerPasswordValidator, Configurations.ResourceOwnerPasswordValidator>();
        services.AddTransient<IProfileService, Configurations.ProfileService>();

        services.AddMvc();

在配置下:

app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
        {
            Authority = "http://localhost:5000",
            RequireHttpsMetadata = false,
            ApiSecret = "TopSecret",
            AutomaticAuthenticate = true,
            AutomaticChallenge = false,

            ApiName = "MyAPI"
        });

        InitializeDatabase(app);

        app.UseIdentityServer();

        app.UseMvc();

请注意,我只是在开始遇到问题以努力使其正常工作后才将 ApiSecret、AutomaticAuthenticate 和 AutomaticChallenge 添加到此部分。

在我的 Scopes.cs 中,我概述了以下 API:

public static IEnumerable<ApiResource> GetApiResources()
    {
        return new[]
        {
            new ApiResource
            {
                Name = "MyAPI",
                DisplayName = "My API",
                ApiSecrets =
                {
                    new Secret("TopSecret".Sha256()),
                },

            }
        };            
    }

对于 Clients.cs:

public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
    {

        new Client
        {
            ClientName = "My Client",
            AlwaysSendClientClaims=true,                
            ClientId = "MyClient",
            ClientSecrets = { new Secret("TopSecret".Sha256()) },
            RequireClientSecret=false,
            AllowAccessTokensViaBrowser =true,
            AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
            AllowedScopes = { "MyAPI" },
            RequireConsent = false,
            AllowOfflineAccess = true,

        },

这或多或少是代码部分的全部内容。包含配置的数据库似乎覆盖了我所做的任何代码更改,所以我不确定这一切有多大用处。在数据库中,我在 ApiSecrets 表中创建了一条 ApiResourceId 为 1 的记录,添加了描述和到期日期,将类型设置为“SharedSecret”,并使用各种格式添加了 Secret,包括纯文本、sha256 和 base64。

这是通话期间的完整日志。也许它会有所帮助。我看到有一些关于 Bearer 未被发现或类似的事情,但我不确定为什么会这样,以及它是否会影响程序的结果。

    info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 29.4277ms 401
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 POST http://localhost:5000/connect/introspect application/x-www-form-urlencoded 762
info: IdentityServer4.AccessTokenValidation.Infrastructure.NopAuthenticationMiddleware[7]
      Bearer was not authenticated. Failure message: No token found.
dbug: IdentityServer4.CorsPolicyProvider[0]
      CORS request made for path: /connect/introspect from origin: chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo but rejected because invalid CORS path
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware[7]
      idsrv was not authenticated. Failure message: Unprotect ticket failed
dbug: IdentityServer4.Hosting.EndpointRouter[0]
      Request path /connect/introspect matched to endpoint type Introspection
dbug: IdentityServer4.Hosting.EndpointRouter[0]
      Mapping found for endpoint: Introspection, creating handler: IdentityServer4.Endpoints.IntrospectionEndpoint
info: IdentityServer4.Hosting.IdentityServerMiddleware[0]
      Invoking IdentityServer endpoint: IdentityServer4.Endpoints.IntrospectionEndpoint for /connect/introspect
dbug: IdentityServer4.Endpoints.IntrospectionEndpoint[0]
      Starting introspection request.
dbug: IdentityServer4.Validation.BasicAuthenticationSecretParser[0]
      Start parsing Basic Authentication secret
dbug: IdentityServer4.Validation.SecretParser[0]
      Parser found secret: BasicAuthenticationSecretParser
dbug: IdentityServer4.Validation.SecretParser[0]
      Secret id found: MyAPI
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1]
      Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30']
      SELECT TOP(1) [apiResource].[Id], [apiResource].[Description], [apiResource].[DisplayName], [apiResource].[Enabled], [apiResource].[Name]
      FROM [ApiResources] AS [apiResource]
      WHERE [apiResource].[Name] = @__name_0
      ORDER BY [apiResource].[Id]
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1]
      Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30']
      SELECT [a3].[Id], [a3].[ApiResourceId], [a3].[Type]
      FROM [ApiClaims] AS [a3]
      INNER JOIN (
          SELECT DISTINCT TOP(1) [apiResource].[Id]
          FROM [ApiResources] AS [apiResource]
          WHERE [apiResource].[Name] = @__name_0
          ORDER BY [apiResource].[Id]
      ) AS [apiResource2] ON [a3].[ApiResourceId] = [apiResource2].[Id]
      ORDER BY [apiResource2].[Id]
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1]
      Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30']
      SELECT [a2].[Id], [a2].[ApiResourceId], [a2].[Description], [a2].[Expiration], [a2].[Type], [a2].[Value]
      FROM [ApiSecrets] AS [a2]
      INNER JOIN (
          SELECT DISTINCT TOP(1) [apiResource].[Id]
          FROM [ApiResources] AS [apiResource]
          WHERE [apiResource].[Name] = @__name_0
          ORDER BY [apiResource].[Id]
      ) AS [apiResource1] ON [a2].[ApiResourceId] = [apiResource1].[Id]
      ORDER BY [apiResource1].[Id]
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1]
      Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30']
      SELECT [a].[Id], [a].[ApiResourceId], [a].[Description], [a].[DisplayName], [a].[Emphasize], [a].[Name], [a].[Required], [a].[ShowInDiscoveryDocument]
      FROM [ApiScopes] AS [a]
      INNER JOIN (
          SELECT DISTINCT TOP(1) [apiResource].[Id]
          FROM [ApiResources] AS [apiResource]
          WHERE [apiResource].[Name] = @__name_0
          ORDER BY [apiResource].[Id]
      ) AS [apiResource0] ON [a].[ApiResourceId] = [apiResource0].[Id]
      ORDER BY [apiResource0].[Id], [a].[Id]
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1]
      Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30']
      SELECT [a0].[Id], [a0].[ApiScopeId], [a0].[Type]
      FROM [ApiScopeClaims] AS [a0]
      INNER JOIN (
          SELECT DISTINCT [apiResource0].[Id], [a].[Id] AS [Id0]
          FROM [ApiScopes] AS [a]
          INNER JOIN (
              SELECT DISTINCT TOP(1) [apiResource].[Id]
              FROM [ApiResources] AS [apiResource]
              WHERE [apiResource].[Name] = @__name_0
              ORDER BY [apiResource].[Id]
          ) AS [apiResource0] ON [a].[ApiResourceId] = [apiResource0].[Id]
      ) AS [a1] ON [a0].[ApiScopeId] = [a1].[Id0]
      ORDER BY [a1].[Id], [a1].[Id0]
dbug: IdentityServer4.EntityFramework.Stores.ResourceStore[0]
      Found MyAPI API resource in database
info: IdentityServer4.Validation.HashedSharedSecretValidator[0]
      Secret: MyAPI Secret uses invalid hashing algorithm.
info: IdentityServer4.Validation.HashedSharedSecretValidator[0]
      Secret: MyAPI Secret uses invalid hashing algorithm.
dbug: IdentityServer4.Validation.SecretValidator[0]
      Secret validators could not validate secret
fail: IdentityServer4.Validation.ApiSecretValidator[0]
      API validation failed.
fail: IdentityServer4.Endpoints.IntrospectionEndpoint[0]
      API unauthorized to call introspection endpoint. aborting.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 30.673ms 401

最佳答案

如果不了解代码和数据库配置的每一个细节,就会有点困难。此外,我没有看到您实际调用内省(introspection)端点的代码。您是使用 C# 还是使用 Javascript 还是使用 Postman 执行此操作?

无论如何,这是我的评论...

Startup.cs

ConfigureServices 方法看起来不错。对于所述问题,不需要添加 ResourceOwner 密码验证器服务;要访问 Introspection 端点,需要 ApiSecret,而不是 ResourceOwner 密码。我假设您出于某种不相关的原因将它放在那里,如果没有,则将其取出。

在 Configure 方法下,您有 app.UseIdentityServerAuthentication,这意味着您使用 Web 应用程序不仅充当身份验证服务器(使用 IdentityServer4),而且还是您的 Web Api 应用程序调用返回到身份验证服务器(在这种情况下它本身)以验证传入的 token 。

app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
    Authority = "https://localhost:44388",
    RequireHttpsMetadata = false,
    ApiName = "MyAPI"
    //ApiSecret = "TopSecret" not necessary to know the api secret for normal validation
    //AutomaticAuthenticate = true, not necessary
    //AutomaticChallenge = false not necessary
});

您可能还想app.UseMvcWithDefaultRoute()

Api InMemory 配置

使用 new ApiResource("name", "display") 构造函数将正确设置数据库;但是像上面那样使用对象初始值设定项语法不会。这是向 GitHub 报告的问题:https://github.com/IdentityServer/IdentityServer4/issues/836

public static IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        // this will incorrectly leave out the ApiScope record in the database, but will create the ApiResoure and ApiSecret records
        new ApiResource
        {
            Name = "MyAPI",
            DisplayName = "My API",
            ApiSecrets =
            {
                new Secret("TopSecret".Sha256()),
            }
        },
        // this will correctly create the ApiResource, ApiScope, and ApiSecret records in the database.
        new ApiResource("MyAPI2", "My API2")
        {
            ApiSecrets =
            {
                new Secret("TopSecret2".Sha256())
            }
        }
    };
}

仅供引用,由于在上面的 new ApiResources 中没有指定范围,IdentityServer 工具将为每个 ApiResource 自动生成一个 ApiScope。在这种情况下,ApiScore 与 ApiResource 同名。一个 ApiResource 必须至少有一个 ApiScope;但可以有很多。然后是与 ClientScopes 表中的客户端相关的 ApiScopes。

客户端内存配置

查看评论...

public static IEnumerable<Client> GetClients()
{
    return new List<Client>
    {
        new Client
        {
            ClientName = "My Client",
            AlwaysSendClientClaims = true,
            ClientId = "MyClient",
            // changed the secret to make clear this is unrelated to the Api secret
            ClientSecrets = { new Secret("TopSecretClientSecret".Sha256()) },
            // RequireClientSecret might as well be true if you are giving this client a secret
            RequireClientSecret = true,
            AllowAccessTokensViaBrowser = true,
            AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
            // Added MyAPI2 from my example above
            AllowedScopes = { "MyAPI", "MyAPI2" },
            RequireConsent = false,
            AllowOfflineAccess = true
        }
    };
}

调用内省(introspection)端点

以下代码来自 WebApi Controller 。 (请记住,在本次讨论中,IdentityServer 权限和 ApiResource 托管在同一个 Web 应用程序中)。对此方法的请求将由客户端发出。

在此方法中,您可以看到它调用其权限的内省(introspection)端点来验证/解密 access_token。在此示例中,这不是必需的,因为我们将 Web 应用程序设置为 app.UseIdentityServerAuthentication,它已经这样做了。自省(introspection)端点将用于 Reference tokens或者当网络应用程序本身无法验证 access_token 时。

[Route("api/[controller]/[action]")]
[Produces("application/json")]
public class DataController : Controller
{
    [HttpGet]
    [Authorize]
    public async Task<IEnumerable<String>> Secure()
    {
        var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");

        var introspectionClient = new IntrospectionClient("https://localhost:44388/connect/introspect", "MyAPI", "TopSecret");

        var response = await introspectionClient.SendAsync(new IntrospectionRequest { Token = accessToken });

        var isActive = response.IsActive;
        var claims = response.Claims;

        return new[] { "secure1", "secure2", $"isActive: {isActive}", JsonConvert.SerializeObject(claims) };
    }
}

在这里,对 ApiScope“MyAPI”使用 IntrospectionClient 会给出 401,因为由于前面提到的对象初始化程序问题,数据库缺少 ApiScope。

最后一件事

另一个可能的问题是,在数据库编辑器中手动添加散列的 ApiSecret 可能会导致奇怪的复制/粘贴问题,从而导致无法正确解密文本。

查看完整解决方案:

https://github.com/travisjs/AspNetCore-IdentityServer-Instrospection

我希望这可以帮助弄清问题的真相,或者至少激发一个新的想法。

关于c# - IdentityServer4 Introspection Endpoint API 使用无效的散列算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42495331/

相关文章:

c# - 为什么这个 TypeConverter 不工作?

javascript - Angular 提示缺少一个根本没有声明的模块我

c# - MEF 如何确定其导入顺序?

asp.net-core - 无法从客户端访问 IdentityServer4 文档示例中的 UserInfo 端点

c# - IdentityModel2 TokenClient 资源所有者密码

c# - 幻灯片壁纸 Windows 7

c# - 读取客户端证书容器名称

identityserver4 - 我应该将 IdentityServer 和它的管理合并到一个应用程序中还是分成两个应用程序?

c# - .NET Core 2/Identity Server 4 - 刷新所有声明

asp.net - 未在配置文件服务上调用 IdentityServer IsActiveAsync 方法