c# - 使用 Swagger 在 ASP.NET Core 中实现 OAuth
问题描述
我想在我的 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
的 ashttp://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
};
});
我想在使用有效凭据登录后重定向回招摇 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 请帮助我。
解决方案
首先,让我为您的图片添加一些细节:
- 您有两个应用程序,一个带有 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 服务器授权端点。请记住,授权端点和登录页面是不同的端点。我们可以通过访问 url 获取前端应用程序的所有已知端点:https://lvh.me:4201/.well-known/openid-configuration
以防我们的应用程序使用 ASP.NET Core 和 IdentityServer。
接下来,Startup.cs
我们的 API 应用程序应该包含:
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 政策
- Awailable 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
并按“授权”按钮以获取如下对话框:
输入您的秘密,标记范围并按“授权”,在您进行身份验证后,您将获得:
如果您没有获得成功的结果,请查看前端应用程序的日志,通常它包含可以帮助您找出问题的错误。
希望以上文字对您有所帮助。
推荐阅读
- bash - 在没有参数的情况下运行自定义脚本
- android - 如何通过 Android 项目构建修复 Gradle 插件错误?
- reactjs - 如何在 React 中设置 select 的初始值?
- java - 如何在 ApplicationContext.xml 中定义一个没有构造函数的 bean?
- python - 通过 Python 发送 Outlook 电子邮件时需要收据
- reactjs - React Jest 测试错误消息必须可以静态评估才能提取
- h2 - azkaban h2.mv.db 太大了,有问题吗?
- javascript - 修复另一个可滚动 div 右上角的 div
- javascript - 从第二列开始填充数据表
- grafana - 如何使用 API 发布 grafana dashbord?