使用 Swagger 在 ASP.NET Core 中实现 OAuth

问题描述

我想在我的网络应用程序中实现 OAuth,为此我在我的 startup.cs

添加了以下代码
public static IServiceCollection AddSwaggerDocumentation(this IServiceCollection services)
        {
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1",new OpenApiInfo { Title = "CombiTime API v1.0",Version = "v1" });

                c.AddSecurityDeFinition("OAuth2",new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.OAuth2,Flows = new OpenApiOAuthFlows
                    {
                        AuthorizationCode = new OpenApiOAuthFlow
                        {
                            AuthorizationUrl = new Uri("http://localhost:4200/login"),TokenUrl = new Uri("http://localhost:4200/connect/token")
                        }
                    }
                });
                c.OperationFilter<AuthorizeOperationFilter>();

                c.AddSecurityRequirement(new OpenApiSecurityRequirement{
                    {
                        new OpenApiSecurityScheme{
                            Reference = new OpenApiReference{
                                Id = "Bearer",//The name of the prevIoUsly defined security scheme.
                                Type = ReferenceType.SecurityScheme
                            }
                        },new List<string>()
                    }
                });
            });

            return services;
        }

        public static IApplicationBuilder UseSwaggerDocumentation(this IApplicationBuilder app)
        {
            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json","Versioned API v1.0");
                c.DocumentTitle = "Title Documentation";
                c.DocExpansion(DocExpansion.None);
                c.RoutePrefix = string.Empty;
                c.OAuthClientId("combitimeapi_swagger");
                c.OAuthAppName("Combitime API");
                c.OAuthUsePkce();
            });

            return app;
        }

AuthorizeOperationFilter代码如下:

public void Apply(OpenApiOperation operation,OperationFilterContext context)
        {
            // Since all the operations in our api are protected,we need not
            // check separately if the operation has Authorize attribute
            operation.Responses.Add("401",new OpenApiResponse { Description = "Unauthorized" });
            operation.Responses.Add("403",new OpenApiResponse { Description = "Forbidden" });

            operation.Security = new List<OpenApiSecurityRequirement>
            {
                new OpenApiSecurityRequirement
                {
                    [
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference {Type = ReferenceType.SecurityScheme,Id = "oauth2"}
                        }
                    ] = new[] {"combitimeapi"}
                }
            };
        }

通过使用此代码,我会在我的 swagger UI 上获得一个“授权”按钮,当我单击该按钮时,我将重定向到我的登录页面(基于 angular 的前端)。所以我把我的 AuthorizationUrl 设为 http://localhost:4200/login 然后当我被重定向登录页面时,我使用有效的凭据登录,我使用了 jwt 令牌进行登录,为此我在我的 {{ 1}}

startup.cs

我想在使用有效凭据登录重定向回 swagger UI,但问题是我在登录后被重定向到仪表板。请帮助我或让我知道我做错了什么。

我从 swagger 重定向登录页面后形成的 url 是:

services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = false;
                x.Savetoken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,IssuerSigningKey = new SymmetricSecurityKey(key),ValidateIssuer = false,ValidateAudience = false
                };
            });

我的前端在端口 4200 上运行。 我的 swagger 在端口 61574 上运行。 但是在输入有效凭据后,我并没有被重定向到 swagger UI 请帮帮我。

解决方法

如果您查看 OAuth 网站,该案例被描述为每请求自定义

按请求定制

开发人员经常会认为他们需要能够使用 每个授权请求上的不同重定向 URL,并将尝试 更改每个请求的查询字符串参数。这不是 重定向 URL 的预期用途,不应被 授权服务器。服务器应拒绝任何授权 重定向 URL 不完全匹配的请求 注册网址。

如果客户端希望在重定向 URL 中包含特定于请求的数据,它可以 > 改为使用“状态”参数来存储将在 > 用户重定向后包含的数据。它既可以在状态参数本身中对数据进行编码,也可以使用状态参数作为会话 ID 将状态存储在服务器上。

希望对您有所帮助。

来源:https://www.oauth.com/oauth2-servers/redirect-uris/redirect-uri-registration/

,

首先,让我为您的图片添加一些细节:

  1. 您有两个应用程序,一个带有 API(基于 ASP.NET Core),另一个带有前端 UI(Angular,但无所谓),而且重要的是带有授权/身份验证功能。
  2. 您使用 .NETCore 3.1
  3. 您为 swagger 配置授权,这意味着来自 swagger UI 页面的任何调用都将使用给定的授权参数。

因此,对于 API 应用程序,我们必须添加一个具有配置我们 swagger 的辅助方法的类:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddSwaggerDocumentation(this IServiceCollection services)
    {
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1",new OpenApiInfo { Title = "CombiTime API v1.0",Version = "v1" });

            c.AddSecurityDefinition(
                "oauth2",new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.OAuth2,Flows = new OpenApiOAuthFlows
                    {
                        AuthorizationCode = new OpenApiOAuthFlow
                        {
                            AuthorizationUrl = new Uri("https://lvh.me:4201/connect/authorize"),TokenUrl = new Uri("https://lvh.me:4201/connect/token"),Scopes = new Dictionary<string,string> {
                                { "combitimeapi","Demo API" }
                            }
                        }
                    }
                });
            c.OperationFilter<AuthorizeOperationFilter>();

            c.AddSecurityRequirement(
                new OpenApiSecurityRequirement 
                {
                    {
                        new OpenApiSecurityScheme{
                            Reference = new OpenApiReference{
                                Id = "oauth2",//The name of the previously defined security scheme.
                                Type = ReferenceType.SecurityScheme
                            }
                        },new List<string>()
                    }
                });
        });

        return services;
    }

    public static IApplicationBuilder UseSwaggerDocumentation(this IApplicationBuilder app)
    {
        app.UseSwagger();
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json","Versioned API v1.0");
            c.DocumentTitle = "Title Documentation";
            c.DocExpansion(DocExpansion.None);
            c.RoutePrefix = string.Empty;
            c.OAuthClientId("combitimeapi_swagger");
            c.OAuthAppName("Combitime API");
            c.OAuthScopeSeparator(",");
            c.OAuthUsePkce();
        });

        return app;
    }
}

请注意 AuthorizationUrl 属性和 TokenUrl 属性。 AuthorizationUrl 属性应指向我们的 OAuth2 服务器授权端点。请记住,授权端点和登录页面是不同的端点。我们可以通过访问以下 URL 获取前端应用程序的所有已知端点:https://lvh.me:4201/.well-known/openid-configuration,以防我们的应用程序使用 ASP.NET Core 和 IdentityServer。

接下来,我们 API 应用程序的 Startup.cs 应包含:

public void ConfigureServices(IServiceCollection services)
{
    // ... some your code ...

    services.AddSwaggerDocumentation();
    services.AddAuthentication("Bearer")
        .AddIdentityServerAuthentication("Bearer",options =>
        {
            options.ApiName = "combitimeapi";
            options.Authority = "https://lvh.me:4201";
        });

    // ... some your code ...
}

public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
    // ... some your code ...
    app.UseSwaggerDocumentation();
    app.UseRouting();
    app.UseAuthorization();

    // ... some your code ...

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

请不要忘记为您的所有控制器添加属性 [Authorize],因为您的 AuthorizeOperationFilter 假定已完成。

让我们寻找前端和授权部分所需的更改。您应该配置一些特定的东西,例如:

  1. CORS 政策
  2. 可用的 API 客户端(一个是您的 Angular UI,另一个是 API 应用程序)
  3. 可用的 API 资源
  4. 身份验证和授权方法

Startup.cs 应包含:

public void ConfigureServices(IServiceCollection services)
{
    // ... some your code ...

    services.AddCors(policies => {
        policies.AddDefaultPolicy(builder => {
            builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
        });
    });

    services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser,ApplicationDbContext>(options => {
            options.Clients.AddIdentityServerSPA("forntend",cfg => {});
            options.Clients.AddNativeApp("combitimeapi_swagger",cfg => {
                cfg
                    .WithRedirectUri("https://lvh.me:5001/oauth2-redirect.html")
                    .WithScopes("combitimeapi");
            });
            options.ApiResources.AddApiResource("combitimeapi",cfg => {
                cfg.WithScopes("combitimeapi");
            });
        })
        .AddApiResources();

    services
        .AddAuthentication(
            x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
        .AddIdentityServerJwt();
    // ... some your code ...
}

我在这里使用 .AddIdentityServerJwt() 而不是您的 .AddJwtBearer(...),因为我没有您的密钥和其他特定选项。

前端应用程序配置为使用 HTTPS 端口 4201 和 HTTP 端口 4200,API 应用程序配置为 HTTPS 端口 5001 和 HTTP 端口 5000。

现在您可以运行这两个应用程序并转到页面 https://lvh.me:5001/index.html 并按“授权”按钮以获取如下对话框:auth dialog

输入您的秘密,标记范围并按“授权”,在您验证自己后,您将获得:auth dialog

如果你没有得到成功的结果,请检查前端应用程序的日志,通常它包含可以帮助你找出问题的错误。

希望以上文字对您有所帮助。

,

启动代码可能存在多个问题,在 AddSwaggerGen 中更恰当。

身份提供者的配置:

独立于重定向,您是否能够获得访问令牌,或者您是否收到某种错误,例如在请求中或身份提供者本身中?

请注意,您在 Swagger 中提供的客户端配置必须与身份提供者中的配置相匹配。你似乎在关注Scott Brady's example;我们可以观察到他的所有 Swagger's startup configuration 都遵循他在身份服务器 (here) 中的信息。

在对 API 的调用中设置令牌:

此外,即使您获得了令牌,我认为您也没有在从 Swagger 到 API 本身的后续调用中设置它。

AddSecurityDefinitionAddSecurityRequirementAuthorizeOperationFilter 通常至少提到一个具有相同标识符的方案,因为第一个方法定义了 Swagger 的身份验证方式,第二个/第三个方法定义了定义对 API 调用进行身份验证的方式(因此,它们必须相互引用)。但是,您在所有三种方法中都使用了不同的 ID - "OAuth2""Bearer""oauth2" - 所以没有它们是相互关联的。

我不完全了解您的应用程序,但我相信您实际上可能只使用 AddSecurityRequirementAuthorizeOperationFilter 之一,因为它们都指定了安全要求。最重要的是引用 SecurityDefinition 的 ID(在您的情况下,“OAuth2”)。

Scott 的例子,事实上,only uses the AuthorizeCheckOperationFilter 并使用 the same ID for the OpenApiSecurityScheme,即 previously registered in the AddSecurityDefinition - 在他的例子中,"oauth2",但任何名称/字符串都可以使用。