使用 IdentityServer4 的 ASP.NET Core Swagger UI 授权 参考项目Demo
摘要 上述参考项目文档摘要:
Swagger 与 OAuth 授权服务器的集成相对有据可查,因此在本文中,您将了解使用 Swagger 将 IdentityServer 支持添加到 ASP.NET Core API 的基础知识,然后查看这种方法的局限性和一些替代方案可能值得探索。
本文将演示Swashbuckle和NSwag。
准备你的 API 您可以在您的ConfigureServices
方法中注册此身份验证库:
1 2 3 4 5 6 7 8 9 services.AddAuthentication("Bearer" ) .AddIdentityServerAuthentication("Bearer" , options => { options.ApiName = "api1" ; options.Authority = "https://localhost:5000" ; });
Configure
然后通过将您的方法 更新为如下所示在您的 HTTP 管道中启用它:
1 2 3 4 5 6 7 8 9 public void Configure (IApplicationBuilder app ){ app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute()); }
然后,您可以使用AuthorizeAttribute
on 操作或控制器来触发此操作。您现在应该从这些受保护的端点获得 401 Unauthorized。
将 OAuth 支持添加到 Swashbuckle 回到您的 API,让我们引入 Swashbuckle:
1 2 dotnet add package Swashbuckle.AspNetCore dotnet add package Swashbuckle.AspNetCore.Swagger
您可以通过将以下内容添加到您的ConfigureServices
方法来注册:
1 2 3 4 5 6 services.AddSwaggerGen(options => { options.SwaggerDoc("v1" , new OpenApiInfo {Title = "Protected API" , Version = "v1" }); });
这将配置一个带有一些描述性信息的基本 Swagger 文档。
接下来,您想将一些有关您的授权服务器的信息添加到 swagger 文档中。由于您的 UI 将在最终用户的浏览器中运行,并且在该浏览器中运行的 JavaScript 将需要访问令牌,因此您将使用授权代码流(以及稍后用于代码交换的 Proof-Key (PKCE))。
因此,您需要告诉 Swashbuckle 您的授权和令牌端点的位置(检查您的 IdentityServer 迪斯科文档),以及它将使用的范围(其中键是范围本身,值是显示名称)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 options.AddSecurityDefinition("oauth2" , new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, Flows = new OpenApiOAuthFlows { AuthorizationCode = new OpenApiOAuthFlow { AuthorizationUrl = new Uri("https://localhost:5000/connect/authorize" ), TokenUrl = new Uri("https://localhost:5000/connect/token" ), Scopes = new Dictionary<string , string > { {"api1" , "Demo API - full access" } } } } });
您现在需要告诉您的 swagger 文档哪些端点需要访问令牌才能工作,并且它们可以返回 401 和 403 响应。您可以使用 来执行此操作IOperationFilter
,您可以在下面看到它(这已改编自eShopOnContainers 示例存储库中的过滤器)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class AuthorizeCheckOperationFilter : IOperationFilter { public void Apply (OpenApiOperation operation, OperationFilterContext context ) { var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true ).OfType<AuthorizeAttribute>().Any() || context.MethodInfo.GetCustomAttributes(true ).OfType<AuthorizeAttribute>().Any(); if (hasAuthorize) { 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 [] {"api1" } } }; } } }
在这里,您正在寻找所有带有 的控制器和操作AuthorizeAttribute
,并告诉您的 Swagger 文档包括额外的可能响应,并且它需要一个具有特定范围授权的访问令牌,如安全定义中所定义。
然后,您可以像这样注册:
1 options.OperationFilter<AuthorizeCheckOperationFilter>();
现在,您可以通过将以下内容添加到您的Configure
方法中来配置管道中的 Swagger 文档端点和 Swagger UI:
1 2 3 4 5 6 7 8 9 app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json" , "My API V1" ); options.OAuthClientId("demo_api_swagger" ); options.OAuthAppName("Demo API - Swagger" ); options.OAuthUsePkce(); });
在这里,您还要说明您希望 Swagger UI 用于授权请求的客户端 ID、显示名称以及它应该使用 PKCE。
您可以在ASP.NET Core 文档 的 ASP.NET Core API 中找到更全面的配置 Swashbuckle 的演练。
将 OAuth 支持添加到 NSwag 让我们看看如何使用 NSwag 实现同样的效果。第一步是通过 NuGet 引入 NSwag 库:
1 dotnet add package NSwag.AspNetCore
ConfigureServices
现在,您可以通过将注册添加到您的方法 来将 Swagger 文档生成添加到您的项目中。
1 2 3 4 5 6 7 8 services.AddOpenApiDocument(options => { options.DocumentName = "v1" ; options.Title = "Protected API" ; options.Version = "v1" ; });
接下来,您想将一些有关您的授权服务器的信息添加到 swagger 文档中。由于您的 UI 将在最终用户的浏览器中运行,并且在该浏览器中运行的 JavaScript 将需要访问令牌,因此您将使用授权代码流(以及稍后用于代码交换的 Proof-Key (PKCE))。
因此,您需要告诉 Swashbuckle 您的授权和令牌端点的位置(检查您的 IdentityServer 迪斯科文档),以及它将使用的范围(其中键是范围本身,值是显示名称)。
1 2 3 4 5 6 7 8 9 10 11 12 13 options.AddSecurity("oauth2" , new OpenApiSecurityScheme { Type = OpenApiSecuritySchemeType.OAuth2, Flows = new OpenApiOAuthFlows { AuthorizationCode = new OpenApiOAuthFlow { AuthorizationUrl = "https://localhost:5000/connect/authorize" , TokenUrl = "https://localhost:5000/connect/token" , Scopes = new Dictionary<string , string > { { "api1" , "Demo API - full access" } } } } });
为了让 NSwag 了解哪些端点需要访问令牌并向 Swagger 文档添加安全范围,您可以使用OperationSecurityScopeProcessor
该类自动扫描所有控制器和操作以获取AuthorizationAttributes
.
1 options.OperationProcessors.Add(new OperationSecurityScopeProcessor("oauth2" ));
然后,您可以通过将以下内容添加到您的Configure
方法中来启用管道中的 Swagger 文档和 UI:
1 2 3 4 5 6 7 app.UseOpenApi(); app.UseSwaggerUi3(options => { options.OAuth2Client.ClientId = "demo_api_swagger" ; options.OAuth2Client.AppName = "Demo API - Swagger" ; options.OAuth2Client.UsePkceWithAuthorizationCodeGrant = true ; });
在这里,您还要说明您希望 Swagger UI 用于授权请求的客户端 ID、显示名称以及它应该使用 PKCE。
您可以在ASP.NET Core 文档 中找到更全面的在 ASP.NET Core API 中配置 NSwag 的演练。
部分代码 安装Nuget
startup.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 using System;using System.Collections.Generic;using System.Linq;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Builder;using Microsoft.Extensions.DependencyInjection;using Microsoft.OpenApi.Models;using Swashbuckle.AspNetCore.SwaggerGen;namespace Api.Swashbuckle { public class Startup { public void ConfigureServices (IServiceCollection services ) { services.AddControllers(); services.AddCors(options => { options.AddPolicy("AllowAllOrigins" , builder => { builder .AllowCredentials() .WithOrigins("http://localhost:44357" , "https://localhost:44357" ) .SetIsOriginAllowedToAllowWildcardSubdomains() .AllowAnyHeader() .AllowAnyMethod(); }); }); services.AddAuthentication("Bearer" ) .AddIdentityServerAuthentication("Bearer" , options => { options.ApiName = "Demo_Api_Source" ; options.Authority = "https://localhost:44310" ; }); services.AddSwaggerGen(options => { options.SwaggerDoc("v1" , new OpenApiInfo {Title = "Demo.SwaggerUi" , Version = "v1" }); options.AddSecurityDefinition("oauth2" , new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, Flows = new OpenApiOAuthFlows { AuthorizationCode = new OpenApiOAuthFlow { AuthorizationUrl = new Uri("https://localhost:44310/connect/authorize" ), TokenUrl = new Uri("https://localhost:44310/connect/token" ), Scopes = new Dictionary<string , string > { { "Demo_Api_Scopes" , "Demo Simple Scopes" } }, }, } }); options.OperationFilter<AuthorizeCheckOperationFilter>(); }); } public void Configure (IApplicationBuilder app ) { app.UseDeveloperExceptionPage(); app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json" , "My API" ); options.OAuthClientId("Demo_Api_SwaggerUi" ); options.OAuthAppName("Demo API - Swagger" ); options.OAuthUsePkce(); }); app.UseCors("AllowAllOrigins" ); app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute()); } } public class AuthorizeCheckOperationFilter : IOperationFilter { public void Apply (OpenApiOperation operation, OperationFilterContext context ) { var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true ).OfType<AuthorizeAttribute>().Any() || context.MethodInfo.GetCustomAttributes(true ).OfType<AuthorizeAttribute>().Any(); if (hasAuthorize) { 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 [] { "Demo_Api_Scopes" } } }; } } } }
WeatherForecastController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 using System;using System.Collections.Generic;using System.Linq;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Mvc;namespace Api.Swashbuckle.Controllers { [Authorize ] [ApiController ] [Route("[controller]" ) ] public class WeatherForecastController : ControllerBase { private static readonly string [] Summaries = { "Freezing" , "Bracing" , "Chilly" , "Cool" , "Mild" , "Warm" , "Balmy" , "Hot" , "Sweltering" , "Scorching" }; [HttpGet ] public IEnumerable<WeatherForecast> Get () { var rng = new Random(); return Enumerable.Range(1 , 5 ).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20 , 55 ), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } } }
附 Token在线解析工具:https://tooltt.com/jwt-decode/
Tips token中的aud:Ids4 toke 中设置aud:需要在api Scopes 中设置对应资源,并且在客户端中引用
The End.