使用 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 的前端)。所以我给了我的AuthorizationUrl
http://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 本身的后续调用中设置它。
AddSecurityDefinition
和AddSecurityRequirement
或AuthorizeOperationFilter
通常提到至少一个具有相同标识符的方案,因为第一个方法定义了 Swagger 的身份验证方式,而第二个/第三个定义了调用的方式API 已通过身份验证(因此,它们必须相互引用)。但是,您在所有三种方法中都使用了不同的 ID - "OAuth2"、"Bearer" 和 "oauth2" - 所以没有一个它们是链接的。
我不完全了解您的应用程序,但我相信您实际上可能只使用AddSecurityRequirement
或AuthorizeOperationFilter
之一,因为它们都指定了安全要求。最重要的是引用 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说明文档看这篇就够了