客户端凭据流:无法理解整个设置 代码

问题描述

我目前正在尝试为我的公司设置一个基于 OpenIddict 的 AuthServer。我目前正在努力从我的测试 API 之一访问安全端点。

未来需要的解决方

从长远来看,我想实现的目标: 基于 OpenIddict 的集中式身份验证服务器,我的同事可以在其中注册他们的 API/客户端/软件项目以进行安全访问。为简单起见,我调用我同事 API1 的 API 之一。这个 API1 有一堆控制器和端点,需要使用 Auth-Server 进行保护。几个端点需要不同的保护级别。因此,API1 的 Client1 仅获取读取端点的凭据,而不是 Client2 获取读取和写入端点的凭据。

现状及问题

现在我想从一个简单的例子开始,我有一个 Auth-Server 和一个 API1,客户端现在是 Postman。

我在 Postman 中使用 OAuth2 方法获得了一个有效的令牌,这里是 Auth-Server 的日志:

    info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The request address matched a server endpoint: Token.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The token request was successfully extracted: {
        "grant_type": "client_credentials","scope": "","client_id": "resource_server_1","client_secret": "[redacted]"
      }.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The token request was successfully validated.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The response was successfully returned as a JSON document: {
        "access_token": "[redacted]","token_type": "Bearer","expires_in": 3599
      }.

但是当我在 Postman 中使用此令牌访问 API1 中的授权端点时,我从 API1 收到此错误

    info: OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandler[12]
      AuthenticationScheme: OpenIddict.Validation.AspNetCore was challenged.
info: System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.LogicalHandler[100]
      Start processing HTTP request GET https://localhost:5001/.well-kNown/openid-configuration
info: System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.ClientHandler[100]
      Sending HTTP request GET https://localhost:5001/.well-kNown/openid-configuration
info: System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.ClientHandler[101]
      Received HTTP response after 326.6051ms - OK
info: System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.LogicalHandler[101]
      End processing HTTP request after 366.9958ms - OK
info: System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.LogicalHandler[100]
      Start processing HTTP request GET https://localhost:5001/.well-kNown/jwks
info: System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.ClientHandler[100]
      Sending HTTP request GET https://localhost:5001/.well-kNown/jwks
info: System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.ClientHandler[101]
      Received HTTP response after 76.1564ms - OK
info: System.Net.Http.HttpClient.OpenIddict.Validation.SystemNetHttp.LogicalHandler[101]
      End processing HTTP request after 83.7518ms - OK
info: OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandler[7]
      OpenIddict.Validation.AspNetCore was not authenticated. Failure message: An error occurred while authenticating the current request.
info: OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandler[7]
      OpenIddict.Validation.AspNetCore was not authenticated. Failure message: An error occurred while authenticating the current request.
info: OpenIddict.Validation.OpenIddictValidationdispatcher[0]
      The response was successfully returned as a JSON document: {
        "error": "server_error","error_description": "This resource server is currently unavailable.","error_uri": "https://documentation.openiddict.com/errors/ID2092"
      }.
info: OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandler[12]
      AuthenticationScheme: OpenIddict.Validation.AspNetCore was challenged.

认证服务器吐出这个:

    info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The request address matched a server endpoint: Configuration.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The configuration request was successfully extracted: {}.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The configuration request was successfully validated.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The response was successfully returned as a JSON document: {
        "issuer": "https://localhost:5001/","token_endpoint": "https://localhost:5001/connect/token","jwks_uri": "https://localhost:5001/.well-kNown/jwks","grant_types_supported": [
          "client_credentials"
        ],"scopes_supported": [
          "openid","scp:profile"
        ],"claims_supported": [
          "aud","exp","iat","iss","sub"
        ],"id_token_signing_alg_values_supported": [
          "RS256"
        ],"subject_types_supported": [
          "public"
        ],"token_endpoint_auth_methods_supported": [
          "client_secret_basic","client_secret_post"
        ],"claims_parameter_supported": false,"request_parameter_supported": false,"request_uri_parameter_supported": false
      }.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The request address matched a server endpoint: Cryptography.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The cryptography request was successfully extracted: {}.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The cryptography request was successfully validated.
info: OpenIddict.Server.OpenIddictServerdispatcher[0]
      The response was successfully returned as a JSON document: {
        "keys": [
          {
            "kid": "B0A4787E6E637564D164006A0E48F5C4FB1285BC","use": "sig","kty": "RSA","alg": "RS256","e": "AQAB","n": "{VERY-LONG-STRING-HERE}","x5t": "sKR4fm5jdWTRZABqDkj1xPsShbw","x5c": [
              "{VERY-LONG-STRING-HERE}"
            ]
          }
        ]
      }.

代码

这是我从 Auth-Server 启动的:

    using Cece.Server.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace Cece.Server
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            services.AddDbContext<ApplicationDbContext>(options =>
            {
                // Configure the context to use Microsoft sql Server.
                options.UsesqlServer(Configuration.GetConnectionString("DefaultConnection"));

                // Register the entity sets needed by OpenIddict.
                // Note: use the generic overload if you need
                // to replace the default OpenIddict entities.
                options.USEOpenIddict();
            });

            services.AddOpenIddict()

                // Register the OpenIddict core components.
                .AddCore(options =>
                {
                    // Configure OpenIddict to use the Entity Framework Core stores and models.
                    // Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
                    options.UseEntityFrameworkCore()
                           .UseDbContext<ApplicationDbContext>();
                })

                // Register the OpenIddict server components.
                .AddServer(options =>
                {
                    // Enable the token endpoint.
                    options.SetTokenEndpointUris("/connect/token");

                    // Enable the client credentials flow.
                    options.AllowClientCredentialsFlow();

                    // Register the signing and encryption credentials.
                    options.AddDevelopmentEncryptionCertificate()
                           .AddDevelopmentSigningCertificate();

                    // Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
                    options.UseAspNetCore()
                           .EnabletokenEndpointPassthrough();

                    options.RegisterScopes(OpenIddictConstants.Permissions.Scopes.Profile);
                })

                // Register the OpenIddict validation components.
                .AddValidation(options =>
                {
                    // Import the configuration from the local OpenIddict server instance.
                    options.UseLocalServer();

                    // Register the ASP.NET Core host.
                    options.UseAspNetCore();
                });

            // Register the worker responsible of seeding the database with the sample clients.
            // Note: in a real world application,this step should be part of a setup script.
            services.AddHostedService<Worker>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
        {
            app.UseDeveloperExceptionPage();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(options =>
            {
                options.MapControllers();
                options.MapDefaultControllerRoute();
            });

            app.UseWelcomePage();
        }
    }
}

以及 Auth-Server 中的 worker:

    using Cece.Server.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenIddict.Abstractions;
using System;
using System.Threading;
using System.Threading.Tasks;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace Cece.Server
{
    internal class Worker : IHostedService
    {
        private readonly IServiceProvider _serviceProvider;

        public Worker(IServiceProvider serviceProvider)
            => _serviceProvider = serviceProvider;

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            using var scope = _serviceProvider.CreateScope();

            var context = scope.ServiceProvider.GetrequiredService<ApplicationDbContext>();
            await context.Database.EnsureCreatedAsync();

            await CreateApplicationsAsync();
            await CreateScopesAsync();

            async Task CreateApplicationsAsync()
            {
                var manager = scope.ServiceProvider.GetrequiredService<IOpenIddictApplicationManager>();

                if (await manager.FindByClientIdAsync("cece") == null)
                {
                    var descriptor = new OpenIddictApplicationDescriptor
                    {
                        ClientId = "cece",displayName = "Cece client application",Permissions =
                        {
                            Permissions.Endpoints.Authorization,Permissions.Endpoints.logout,Permissions.GrantTypes.ClientCredentials,Permissions.ResponseTypes.IdToken,Permissions.ResponseTypes.IdTokenToken,Permissions.ResponseTypes.Token,Permissions.Scopes.Email,Permissions.Scopes.Profile,Permissions.Scopes.Roles,Permissions.Prefixes.Scope + "api1"
                        }
                    };

                    await manager.CreateAsync(descriptor);
                }

                if (await manager.FindByClientIdAsync("resource_server_1") == null)
                {
                    var descriptor = new OpenIddictApplicationDescriptor
                    {
                        ClientId = "resource_server_1",ClientSecret = "My-Secret",Permissions =
                        {
                            Permissions.GrantTypes.ClientCredentials,Permissions.Endpoints.Token,Permissions.Endpoints.Introspection
                        }
                    };

                    await manager.CreateAsync(descriptor);
                }

                // Note: no client registration is created for resource_server_2
                // as it uses local token validation instead of introspection.
            }

            async Task CreateScopesAsync()
            {
                var manager = scope.ServiceProvider.GetrequiredService<IOpenIddictScopeManager>();

                if (await manager.FindByNameAsync("api1") == null)
                {
                    var descriptor = new OpenIddictScopeDescriptor
                    {
                        Name = "api1",Resources =
                        {
                            "resource_server_1"
                        }
                    };

                    await manager.CreateAsync(descriptor);
                }
            }
        }

        public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
    }
}

这是我从 API1 启动的:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenIddict.Validation.AspNetCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Cece.TestApi1
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
            });

            // Register the OpenIddict validation components.
            services.AddOpenIddict()
                .AddValidation(options =>
                {
                    // Note: the validation handler uses OpenID Connect discovery
                    // to retrieve the address of the introspection endpoint.
                    options.SetIssuer("https://localhost:5001/");
                    options.AddAudiences("resource_server_1");

                    // Configure the validation handler to use introspection and register the client
                    // credentials used when communicating with the remote introspection endpoint.
                    options.UseIntrospection()
                           .SetClientId("resource_server_1")
                           .SetClientSecret("My-Secret");

                    // Register the System.Net.Http integration.
                    options.UseSystemNetHttp();

                    // Register the ASP.NET Core host.
                    options.UseAspNetCore();
                });

            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

问题

所以我的问题是:

  1. 关于上述所需的设置,我是否走在正确的轨道上?
  2. 我收到的这个错误究竟是什么意思?

解决方法

为了能够使用内省,您必须通过在服务器选项中为其提供地址来启用内省端点。解决这个问题,这个错误就会消失。