ASP.NET Web Api 不接受自己的 Openiddict JWT

问题描述

我正在使用 Web API 项目学习 ASP.NET Core 中的 OpenID Connect 实现。我的客户目前是邮递员。

上下文(XY 问题): 我希望 Sendgrid 通过身份验证报告 Webhook 数据。 Sendgrid 使用 OAuth 2 流程。我在 Postman 上模拟了使用 Sendgrid Webhook 调用

我按照 a few 教程设置授权服务器,即。将向您颁发令牌的部分,特别是使用基于 EF Core 的临时内存存储。目前,这个解决方案对我来说已经足够了,我必须做更多的研究和原型设计,然后才能成为生产级以便在未来的项目中重复使用。

我可以使用硬编码凭据通过 Postman 成功获取令牌。现在我希望控制器 API 验证由同一服务器发出的令牌。让我展示一些代码

Startup.cs

   public void ConfigureServices(IServiceCollection services)
    {
        ....
        services.AddControllers();
        ....
  
        services.AddDbContext<OpenIddictDbContext>(ef => //OpenIddictDbContext contains no set,I want the framework to handle its own entities

             // Configure the context to use an in-memory store.
             ef.UseInMemoryDatabase(nameof(OpenIddictDbContext))

             // Register the entity sets needed by OpenIddict.
             .USEOpenIddict()
        );

        services.AddOpenIddict(options =>
            options.AddServer(server => server.AddEphemeralEncryptionKey()
                    .AddEphemeralSigningKey() //Not production-grade but I'll deal with this in the future
                    .RegisterScopes("api")
                    .SetTokenEndpointUris("/api/v1/Auth/token")
                    .SetAuthorizationEndpointUris("/api/v1/Auth/authorize")
                    .AllowClientCredentialsFlow()
                    .UseAspNetCore()
                    .EnabletokenEndpointPassthrough())
                .AddCore(core => core.UseEntityFrameworkCore()
                                  .UseDbContext<OpenIddictDbContext>())
                .AddValidation(validation => validation.UseLocalServer()))
            .AddHostedService<OpenIddictHostedService>() //Used only to create the tech-user for client,I prefer not to paste as it's mostly identical to linked tutorial
            .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(); //Security hardening (i.e. trusted authorities) TBD in the next future

}

AuthController.cs(路径相同)

    [HttpPost("token"),Produces("application/json")]
    public async Task<IActionResult> Token(CancellationToken cancellationToken = default)
    {
        var request = HttpContext.GetopenIddictServerRequest();
        if (!request.IsClientCredentialsGrantType())
        {
            throw new NotImplementedException("The specified grant is not implemented.");
        }

        // Note: the client credentials are automatically validated by OpenIddict:
        // if client_id or client_secret are invalid,this action won't be invoked.

        var application =
            await _applicationManager.FindByClientIdAsync(request.ClientId,cancellationToken) ??
            throw new InvalidOperationException("The application cannot be found.");

        // Create a new ClaimsIdentity containing the claims that
        // will be used to create an id_token,a token or a code.
        var identity = new ClaimsIdentity(
            TokenValidationParameters.DefaultAuthenticationType,OpenIddictConstants.Claims.Name,OpenIddictConstants.Claims.Role);

        // Use the client_id as the subject identifier.
        identity.AddClaim(OpenIddictConstants.Claims.Subject,await _applicationManager.GetClientIdAsync(application,cancellationToken),OpenIddictConstants.Destinations.Accesstoken,OpenIddictConstants.Destinations.IdentityToken);

        identity.AddClaim(OpenIddictConstants.Claims.Name,await _applicationManager.GetdisplayNameAsync(application,OpenIddictConstants.Destinations.IdentityToken);

        return SignIn(new ClaimsPrincipal(identity),OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
    }

我的代码与教程中显示代码不同,因为并非所有方法都可以在 OpenIddict 3.0 中找到,也许文档需要更新。

此时,我可以通过 Postman 获取令牌:take and eat; this is my ephemeral token

eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiJWMFZaS1pGR1c0WTJGUUpRNTNMQktaVktXSzZKS1pXV1lDMVREQUpDIiwidhlwIjoiYXQrand0In0.NNnSRN18drc3hj8PXon7bz7SyOpmhBds1fOQVnKZzD9dNSeFOQpc5MNJxkxcSSb3Z-XGGm-2z8Fw03yYJPBn2KkE9zz2udUpWNsWmToiiDOYd_5WVmm3GpZopWAusM-YhzPuavnxY1BFVsIylJs9yX0_jXs1RGIIhNvkGG_AmCBVnPdoDvu41CthRsyJc1IQCX5HuHO2XpvmnaRiVJZyaDbey4pDAs-4Fy-yDosg9w8szUWGcw-Y7e4xYKi6HqmjgDDqJQ14QDW839BOkctdRUrk3GhT7HDZN5Oaq8itPTLoxBrLgG9BE2yqfLKjibWD5MNSpj9OQu8GY-uBFsmt2A.YeBSpHgDu4291YJE-jWhOQ.WjxqjTXykAfFk7NRjXTMjPVsUFOtKK7fJxiLy0T7xiUnPSHIAI9m3UlkmsmQuK7sdxYtZnJ2gw-iJxcd1q1UYYRz5N82bc97py6bTH5cykWwMddCrmlnOFrOqKgTS8cZdgxfPwiz46SZKHYrxotttSU7Jy0YtNNYTBt47TXTMAjFwm81QrVmpPwEdt_wodaar7b23Wlw2SYbparw-hTdY-uaitL3olnCDzIn9Jwc7Zkqz8PwNxLYRPoUz1gpKJdO6c-azHcvYI8yY4Ty0OLdUuAZRGVwM4CRETW-Wixig37Sf6meXohJ1aS2Xe6LWAY5Db-Nyfs1H3D6tXL0hzI-q82xZO2oFNcj-yumYL14FoPlxl4TORgSh3L15rBY7e1VxzoJRPYuXAZUCOHrrdAF_DMnM7TSqe52ckMy2_aKNRsFaLZF8brwi4IDnXpe778Nw-ujTQ-djCPTZ-2gI1BP2h0NbpoSSKwI2_9eeUk_IsF5hq54En9oaQpNmz-7oPUAEa4GBmCeowrvzrtMKGMwPCsaOtYAJRnd63Y3YbTAe-NnGTH0EGvBga_9ElwDWswa-UR8CqeMLCqmDKOq9ryYbL4LWSHp7rmsykd2oHqzu_If8c5wzbLQBp3m-bt3w2EaYROXpLGlNvzVLla78Okwe9jFy8QeGVj1pCU8UsgwWCRzlwY4idV0SBSy8dPbEzCunLGbZgx2W81qXPLxWIybTGuOKyBvNffWXhnriUTjsL5khHrfArtjdbsoP93Ig0nqgOGiNXh82RVAuVVmLqOs6yohxLWUbdlxkiophBCmf8RD8h0Eak1zjmzQGTOS9D2BpWouZuEyZOHftnl07SmhXvZXLdw_gSZCoka4EY3FcIxb-9rw53wQAjn-00JLaMbIEaO79fZGJJytiNTagJJouVmvTCh-luNhJ2TULGC5nTb_szyY9yfvkfjK_tP7WIbW._42bMf01NOZ9tXyalK-ONdrPBDPqq-jL-TjWA2aHSuo

但是每次我尝试调用带有 [Authorize] 注释的控制器时,我都会收到带有标头 WWW-Authenticate: Bearer error="invalid_token" 的 401。

我认为这可能与 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer() 有关,但尝试使用 OpenIddictServerAspNetCoreDefaults.AuthenticationScheme 而不是 JwtBearerDefaults.AuthenticationScheme 会导致以下错误

The OpenIddict ASP.NET Core server handler cannot be used as the default scheme handler.
Make sure that neither DefaultAuthenticateScheme,DefaultChallengeScheme,DefaultForbidScheme,DefaultSignInScheme,DefaultSignOutScheme nor DefaultScheme point to an instance of the OpenIddict ASP.NET Core server handler.

问题

如何使用 Openiddict 使用同一服务器颁发的令牌正确调用AuthorizeAttribute 保护的方法

请注意,我没有找到有关身份验证错误的日志。适当提高日志级别可以是调查的有效第一步

理论

根据我对 OIDC 的了解(我的论文是关于 OAuth 2,所以我可以声称是有能力的)并且通过对代码的了解,这就是我可能缺少的东西。

我已成功实施了 OIDC 周期的授权服务器部分。 IE。使用上面的代码,我终于有了一个端点来验证客户端凭据并调用 SignIn。我的理解是 ASP.NET Core 的 SignIn 方法假定调用代码已经通过匹配客户端 ID 和客户端密码对用户进行了身份验证。

据我了解,ASP.NET Core 不再关心身份验证,SignIn 会释放一个令牌,稍后将在 资源服务器 循环中使用该令牌以进一步进行身份验证调用,因为 Sendgrid 将 Webhooks 发送给我。

所以在这种情况下,我应该努力正确利用 authenticationScheme 参数。

在非 API 方案中(或者如果资源使用者曾经支持)我可以使用 cookie,以便 SignIn 将向 HttpResponse 释放 cookie对象。

或者在这种情况下,我可以找到集成 Openiddict 的资源服务器流的正确方法,或者利用 不同 身份验证器,如 ASP.NET Core 的嵌入式 JWT。

对于后者,SignIn 可以充当委托人。 “嘿,JWT 提供者先生,我在这里成功验证了 Sendrig,他敲我们的门调用我们的身份验证 API。您介意发布一个您将来会接受的有效令牌吗?”。

我没有成功使用

 return SignIn(new ClaimsPrincipal(identity),// OpenIddictServerAspNetCoreDefaults.AuthenticationScheme
           JwtBearerDefaults.AuthenticationScheme
            );

但是上面的代码应该告诉 JWT Provider 为声明的身份释放一个令牌。

解决方法

关键是添加正确身份验证方案

services
.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)

来自example

完整代码片段

        services.AddOpenIddict(options =>
                options.AddServer(server => server.AddEphemeralEncryptionKey()
                        .AddEphemeralSigningKey()
                        .RegisterScopes("api")
                        .SetTokenEndpointUris("/api/v1/Auth/token")
                        .SetAuthorizationEndpointUris("/api/v1/Auth/authorize")
                        .AllowClientCredentialsFlow()
                        .UseAspNetCore()
                        .EnableTokenEndpointPassthrough())
                    .AddCore(core => core.UseEntityFrameworkCore()
                        .UseDbContext<OpenIddictDbContext>())
                    .AddValidation(validation =>
                        validation.UseLocalServer(_ => { })
                            .UseAspNetCore(_ => { })
                    )
            )
            .AddHostedService<OpenIddictHostedService>()
            .AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)
            ;

这也需要我添加 Openiddict.Validation.AspNetCore 作为项目依赖