简体   繁体   English

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

[英]ASP.NET Web Api not accepting own Openiddict JWT

I am learning OpenID Connect implementation in ASP.NET Core with a Web API project.我正在使用 Web API 项目学习 ASP.NET Core 中的 OpenID Connect 实现。 My client is currently Postman.我的客户目前是 Postman。

Context (XY problem): I want Sendgrid to report Webhook data with authentication.上下文(XY 问题):我希望 Sendgrid 报告带有身份验证的 Webhook 数据。 Sendgrid uses OAuth 2 flow. Sendgrid使用OAuth 2流量。 I have mocked a Sendgrid Webhook invocation on Postman to use.我已经模拟了 Postman 上的 Sendgrid Webhook 调用以供使用。

I followeda few tutorials to set up authorization server, ie.按照一些教程来设置授权服务器,即。 the part that will issue you a token, in particular using a temporary in-memory store based on EF Core.将向您颁发令牌的部分,特别是使用基于 EF Core 的临时内存存储。 For the moment, this solution is sufficient to me and I'll have to do more researching and prototyping before becoming production-grade for reuse in future project.目前,这个解决方案对我来说已经足够了,在成为生产级以在未来的项目中重用之前,我必须做更多的研究和原型设计。

I can successfully obtain a token with Postman using hardcoded credentials.我可以使用硬编码凭据成功获得带有 Postman 的令牌。 Now I want the Controller APIs to validate tokens issued by the very same server.现在我想要 Controller API 来验证同一台服务器发出的令牌。 Let me show some code:让我展示一些代码:

Startup.cs启动.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 (identical except for the path) 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, cancellationToken),
            OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken);

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

My code differs from code displayed in the tutorials because not all methods are found with OpenIddict 3.0, maybe the docs need some update.我的代码与教程中显示的代码不同,因为并非所有方法都在 OpenIddict 3.0 中找到,也许文档需要一些更新。

At this point, I can get a token with Postman: take and eat; this is my ephemeral token此时,我可以用Postman得到一个token: take and eat; this is my ephemeral token 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

But every time I try to invoke a controller annotated with [Authorize] I get 401 with header WWW-Authenticate: Bearer error="invalid_token" .但是每次我尝试调用带有[Authorize]注释的 controller 时,我都会得到 401 with header WWW-Authenticate: Bearer error="invalid_token"

I believe that this might have to do with services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer() , but trying to use OpenIddictServerAspNetCoreDefaults.AuthenticationScheme instead of JwtBearerDefaults.AuthenticationScheme results in the following error我相信这可能与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.

Question

How to properly invoke an AuthorizeAttribute -protected method using a token issued by the very same server using Openiddict?如何使用同一服务器使用 Openiddict 颁发的令牌正确调用受AuthorizeAttribute保护的方法?

Note that I find no log about the authentication error.请注意,我没有找到有关身份验证错误的日志。 Raising log level properly can be a valid first step of investigation适当提高日志级别可以是有效调查的第一步

Theory理论

From my knowledge of OIDC (my dissertation was on OAuth 2, so I can claim to be competent) and from perking around the code, here is what I am probably missing.根据我对 OIDC 的了解(我的论文是关于 OAuth 2,所以我可以声称自己是有能力的)并且通过查看代码,这就是我可能遗漏的内容。

I have successfully implemented the authorization server part of OIDC cycle.我已成功实施 OIDC 周期的授权服务器部分。 Ie. IE。 with the code above I finally have an endpoint that validates client credentials and invokes SignIn .使用上面的代码,我终于有了一个端点,可以验证客户端凭据并调用SignIn It is my understanding that ASP.NET Core's SignIn method assumes that calling code has already authenticated the user, by matching client ID and client secret.据我了解,ASP.NET Core 的SignIn方法假定调用代码已经通过匹配客户端 ID 和客户端密码对用户进行了身份验证。

It is my understanding that ASP.NET Core doesn't care anymore about the authentication, the SignIn releases a token that will be used later in the resource server cycle to authenticate further calls, as it is for Sendgrid to send Webhooks to me.据我了解,ASP.NET Core 不再关心身份验证, SignIn会释放一个令牌,稍后将在资源服务器周期中使用该令牌来验证进一步的调用,因为 Sendgrid 会向我发送 Webhook。

So in this case I should probably work on leveraging the authenticationScheme parameter correctly.因此,在这种情况下,我可能应该努力正确利用authenticationScheme参数。

In a non-API scenario (or if the resource consumer ever supported that) I could use a cookie, so that SignIn will release a cookie to the HttpResponse object.在非 API 场景中(或者如果资源消费者支持的话)我可以使用 cookie,这样SignIn就会向HttpResponse object 释放一个 cookie。

Or in this case I could either find the correct way to integrate the resource server flow of Openiddict, or leverage a different authenticator like ASP.NET Core's embedded JWT.或者在这种情况下,我可以找到集成 Openiddict资源服务器流的正确方法,或者利用不同的身份验证器,如 ASP.NET Core 的嵌入式 JWT。

For the latter, SignIn could just act as a delegator.对于后者, SignIn可以充当委托人。 "Hey, Mr. JWT Provider, I successfully authenticated Sendrig here who's knocking at our door calling our authentication API. Would you mind issuing a valid token that you will accept in the future?". “嘿,JWT 供应商先生,我在这里成功验证了 Sendrig,他正在 敲我们的门, 呼叫我们的验证 API。您介意发布一个您将来会接受的有效令牌吗?”。

I was unsuccessful at using我使用不成功

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

But the above code should tell JWT Provider to release a token for the claimed identity.但是上面的代码应该告诉 JWT Provider 为声明的身份释放令牌。

The key was to add the correct authentication scheme关键是添加正确的身份验证方案

services
.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)

From example例子

Full code fragment完整代码片段

        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)
            ;

This also required me to add Openiddict.Validation.AspNetCore as project dependency这也要求我将Openiddict.Validation.AspNetCore添加为项目依赖项

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM