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

Posted

技术标签:

【中文标题】使用 Swagger 在 ASP.NET Core 中实现 OAuth【英文标题】:OAuth Implementation in ASP.NET Core using Swagger 【发布时间】:2021-02-18 18:02:22 【问题描述】:

我想在我的 Web 应用程序中实现 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"
                
            ;
        

通过使用此代码,我在我的招摇 UI 上获得了一个“授权”按钮,当我单击该按钮时,我将重定向到我的登录页面(基于 Angular 的前端)。所以我给了我的AuthorizationUrlhttp://localhost:4200/login,然后当我被重定向到登录页面时,我使用有效的凭据登录,我使用 jwt 令牌进行登录,为此我在startup.cs中添加了以下代码

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

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

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

http://localhost:4200/login?response_type=code&client_id=combitimeapi_swagger&redirect_uri=http:%2F%2Flocalhost:61574%2Foauth2-redirect.html&state=V2VkIEZlYiAxNyAyMDIxIDIyOjU3OjQ2IEdNVCswNTMwIChJbmRpYSBTdGFuZGFyZCBUaW1lKQ%3D%3D&code_challenge=mT0amBTJgczCZmNSZAYVfjzzpaTiGb68XlyR3RNHuas&code_challenge_method=S256

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

【问题讨论】:

有人帮忙吗? 我不知道答案,但我面临同样的问题。只要问题符合条件,我就会提供赏金。 @GoodNightNerdPride 请参考此链接:app.pluralsight.com/…(当您从 microsoft emai-id 登录时,该课程是免费的)。 “asp.net core 和 OAuth 入门”课程定义了所有内容。它帮助了我,现在我可以重定向回来。您可能会像我一样缺少某些东西。 :) @GoodNightNerdPride 另外,重定向在 HTTP 上不起作用,它在 HTTPS 上起作用,因为授权服务器提供的信息,我们的浏览器无法捕获响应并在连接不安全时拒绝。确保您通过 HTTPS 在 localhost 上运行 IdentityServer。 这可能对scottbrady91.com/Identity-Server/…有帮助 【参考方案1】:

如果您查看 OAuth 网站,该案例被描述为 Per-Request Customization

按请求定制

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

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

希望对你的探索有所帮助。

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

【讨论】:

【参考方案2】:

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

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

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

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 服务器授权端点。请记住,授权端点和登录页面是不同的端点。如果我们的应用程序使用 ASP.NET Core 和 IdentityServer,我们可以通过访问 url:https://lvh.me:4201/.well-known/openid-configuration 来获取我们前端应用程序的所有已知端点。

接下来,我们的 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 假定已完成。

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

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

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(...),因为我没有您的密钥和其他特定选项。

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

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

输入您的密码,标记范围并按“授权”,在您进行身份验证后,您将获得:

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

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

【讨论】:

当我关注这个时,我无法在 swagger UI 上点击任何 API。我从授权服务器收到的访问令牌去哪里了?这样做的主要目的是在 IdentityServer 进行身份验证后访问任何 API。但这不起作用.. :( 我认为授权已添加到 API 应用程序中,但从我的解释中并不清楚,因此我将其明确添加到 Startup.cs 到 API 应用程序的 Configure(...)configureServices(...) 方法中 您也没有将安全定义的 ID 与安全要求或 AuthorizeOperation 过滤器匹配。请检查我的答案中的详细信息以解决该部分:) @NPinheiro 感谢您的评论,我之前确实将OAuth2 更改为oauth2,所以它们是匹配的,但没有注意到更多的地方。 当我使用授权代码流时,我无法访问客户端密码,如何解决这个问题?我可以通过 PKCE 流程让它在 Postman 上工作,获取令牌并授权 api,但不能大摇大摆地做到这一点,因为它总是要求提供客户端密码【参考方案3】:

启动代码可能不止一个问题,更恰当的是AddSwaggerGen

身份提供者的配置:

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

请注意,您在 Swagger 中提供的客户端配置必须与 Identity Provider 中的配置相匹配。你似乎在关注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",但可以使用任何名称/字符串。

【讨论】:

以上是关于使用 Swagger 在 ASP.NET Core 中实现 OAuth的主要内容,如果未能解决你的问题,请参考以下文章

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

在 Asp.Net Core 中使用 Swagger 在请求中未发送授权承载令牌

如何在 ASP.NET Core 中更改 Swagger 的基本 url

[Asp.Net Core]ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了

ASP.NET Core Web API中使用Swagger

使用授权标头 (Bearer) 设置 Swagger (ASP.NET Core)