Swashbuckle .NET Core 2 中 JWT 承载的授权

Posted

技术标签:

【中文标题】Swashbuckle .NET Core 2 中 JWT 承载的授权【英文标题】:Authorization for JWT bearer in Swashbuckle .NET Core 2 【发布时间】:2018-08-05 17:12:12 【问题描述】:

我使用身份验证服务为我的应用生成的令牌。那里没有问题。现在我引入了 Swashbuckle 来记录我的 API,我可以通过使用此代码在每个请求中发送 JWT 来进行如下身份验证;

services.AddSwaggerGen(c =>

    var a = new ApiKeyScheme();
    //c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
    // In = "header", Description = "Please insert JWT with Bearer into field", Name = "Authorization", Type = "apiKey" );

    c.OperationFilter<AuthorizationHeaderParameterOperationFilter>();

    c.SwaggerDoc("v2", new Info
    
        Version = "v2",
        Title = "MyTitle",
        Description = "An interface for ...",
        TermsOfService = "None",
        Contact = new Contact()  Name = "MyApp", Email = "a@example.com", Url = "www.example.com" 
    );
    // Set the comments path for the Swagger JSON and UI.
    var basePath = AppContext.BaseDirectory;
    var xmlPath = Path.Combine(basePath, "cpDataCore.xml");
    c.IncludeXmlComments(xmlPath);
);

public class AuthorizationHeaderParameterOperationFilter : IOperationFilter

    public void Apply(Operation operation, OperationFilterContext context)
    
        var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors;
        var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
        var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);

        if (isAuthorized && !allowAnonymous)
        
            if (operation.Parameters == null)
                operation.Parameters = new List<IParameter>();

            operation.Parameters.Add(new NonBodyParameter
            
                Name = "Authorization",
                In = "header",
                Description = "access token",
                Required = true,
                Type = "string"
            );
        
    

这给了我以下标题 - 正如预期的那样

accept:application/json
Accept-Encoding:gzip, deflate, br
Accept-Language:en-AU,en;q=0.9
Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9naXZlbm5hbWUiOiJEZW5uaXMiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9zdXJuYW1lIjoiR2FzY29pZ25lIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6ImRlbm5pc2ciLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9zaWQiOiI1NCIsIlJlZnJlc2hUb2tlbiI6IjY5OTA1NTFmLTNhOTQtNDVmYi1hYjc2LTZlOTQyNGE3NjJmOCIsIkFsbERhdGFSZWFkT25seUZvckFwcHJvdmVycyI6IlRydWUiLCJQcm9qZWN0SUQiOiI2IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoic3lzYWRtaW4iLCJuYmYiOjE1MTk2MzY2NDIsImV4cCI6MTUxOTYzODQ0MiwiaXNzIjoiaHR0cHM6Ly9kYXRhLmNpdmlscHJvc29mdHdhcmUuY29tLyIsImF1ZCI6Imh0dHBzOi8vcm1zLmNpdmlscHJvc29mdHdhcmUuY29tLyJ9.nBEZgzcmZVGhFJmKI8u7p7g7xPU13HEAGJu_lrWylnc
Connection:keep-alive
Cookie:username=demo; jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9naXZlbm5hbWUiOiJUcm95IiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvc3VybmFtZSI6IkVsZGVyIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6InRyb3kiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9zaWQiOiI1IiwiUmVmcmVzaFRva2VuIjoiMTNhNzRmNDQtNmVmOC00MDQ3LTlmYWYtOWQ3MzI4MmNhZjQ4IiwiUHJvamVjdElEIjoiLTEiLCJuYmYiOjE1MDUwOTc3MjEsImV4cCI6MTUwNTA5ODYyMSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo2MDAwMC8iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjYwMDAwLyJ9.8You0XiUlvdHb2TRuDzaiXv6r74v7ga1Av_Z3ikmblU
Host:localhost:60000
Referer:http://localhost:60000/swagger/
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/64.0.3282.167 Safari/537.36

虽然,我不确定 Cookie 是从哪里来的。这与我的代码无关。我只是忽略它 - 到目前为止一切都很好。

问题在于,这意味着每次请求都必须输入令牌,这很痛苦。理想情况下,我想使用内置的 swagger 接口进行身份验证 - 根据几篇文章,我应该能够做到这一点;

c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
 In = "header", Description = "Please insert JWT with Bearer into field", Name = "Authorization", Type = "apiKey" );

这很好,我可以添加令牌,我似乎缺少将令牌添加到每个请求的标头的步骤。如果我只是添加身份验证,那么这会给我以下标头,这当然无法通过身份验证。

GET /api/ApprovalItemTypes HTTP/1.1
Host: localhost:60000
Connection: keep-alive
accept: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36
Referer: http://localhost:60000/swagger/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-AU,en;q=0.9
Cookie: username=demo; jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW5_xxx__LTEiLCJuYmYiOjE1MDUwOTc3MjEsImV4cCI6MTUwNTA5ODYyMSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo2MDAwMC8iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjYwMDAwLyJ9.8You0XiUlvdHb2TRuDzaiXv6r74v7ga1Av_Z3ikmblU

为了让请求包含每个后续请求的令牌,我还需要做什么?

【问题讨论】:

你能分享或标记解决方案吗? 我对以下问题的解决方案。 【参考方案1】:

最后我搬到了 NSwag,所以我不确定最初的问题是什么。 Nswag中的解决方案如下;

    services.AddSwaggerDocument(document =>
        
            document.DocumentName = "CPSwagger";
            document.Title = "My Mobile API";
            document.Version = "v10.3.4";
            document.Description = "An interface for some software.";

            document.DocumentProcessors.Add(
                new SecurityDefinitionAppender("JWT token", new NSwag.OpenApiSecurityScheme
                
                    Type = NSwag.OpenApiSecuritySchemeType.ApiKey,
                    Name = "Authorization",
                    Description = "Copy 'Bearer ' + valid JWT token into field",
                    In = NSwag.OpenApiSecurityApiKeyLocation.Header
                ));
            document.OperationProcessors.Add(new OperationSecurityScopeProcessor("JWT token"));
        );

【讨论】:

【参考方案2】:

如果你在代码中定义

c.AddSecurityDefinition("jwt", new ApiKeyScheme()
 
   In = "header", Description = "Please insert JWT with Bearer into field", Name = "Authorization", Type = "apiKey" );

然后在 .Parameters 中而不是在 .Security 中使用它

operation.Security = new List<IDictionary<string, IEnumerable<string>>> 
            new Dictionary<string, IEnumerable<string>>
            
                "jwt", _scopes 
            

那么一切都会正常:

我和你做的一样,但你应该添加如下(对于 oauth2 或 jwt 不记名令牌身份验证):

    public static class ServiceCollectionExtension

    private static string XmlCommentsFilePath
    
        get
        
            var basePath = PlatformServices.Default.Application.ApplicationBasePath;
            var fileName = Assembly.GetEntryAssembly().GetName().Name + ".xml";
            return Path.Combine(basePath, fileName);
        
    

    public static void AddMySwagger(
        this IServiceCollection services,
        ApiVersion defaultApiVersion,
        Func<ApiVersionDescription, Info> info,
        string authority = null,
        Dictionary<string, string> scopes = null)
    
        services.AddMvcCore().AddVersionedApiExplorer(o => o.GroupNameFormat = "'v'VVV");

        services.AddApiVersioning(o =>
        
            o.ReportApiVersions = true;
            o.AssumeDefaultVersionWhenUnspecified = true;
            o.DefaultApiVersion = defaultApiVersion;
        );

        services.AddSwaggerGen(
            options =>
            
                var provider = services.BuildServiceProvider()
                    .GetRequiredService<IApiVersionDescriptionProvider>();

                foreach (var description in provider.ApiVersionDescriptions)
                
                    if (!description.IsDeprecated)
                        options.SwaggerDoc(description.GroupName, info(description));
                

                options.OperationFilter<DefaultValues>();

                options.IncludeXmlComments(XmlCommentsFilePath);

                if (!string.IsNullOrEmpty(authority))
                
                    options.AddSecurityDefinition("jwt", new ApiKeyScheme()
                    
                        Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer token\"",
                        Name = "Authorization",
                        In = "header",
                        Type = "apiKey"
                    );
                    //options.AddSecurityDefinition("oauth2", new OAuth2Scheme
                    //
                    //  Flow = "implicit",
                    //  AuthorizationUrl = $"authority/connect/authorize",
                    //  Scopes = scopes ?? new Dictionary<string, string>()
                    //);
                    options.OperationFilter<AuthorizeCheckOperationFilter>(scopes?.Select(_ => _.Key).ToList() ?? new List<string>());
                
            );
    

    class AuthorizeCheckOperationFilter : IOperationFilter
    
        private readonly IEnumerable<string> _scopes;

        public AuthorizeCheckOperationFilter(IEnumerable<string> scopes)
        
            _scopes = scopes;
        

        public void Apply(Operation operation, OperationFilterContext context)
        
            var hasAuthorize = context.ApiDescription.ControllerAttributes().OfType<AuthorizeAttribute>().Any() ||
                               context.ApiDescription.ActionAttributes().OfType<AuthorizeAttribute>().Any();

            if (hasAuthorize)
            
                operation.Responses.Add("401", new Response  Description = "Unauthorized" );
                operation.Responses.Add("403", new Response  Description = "Forbidden" );

                operation.Security = new List<IDictionary<string, IEnumerable<string>>> 
                new Dictionary<string, IEnumerable<string>>
                
                    //"oauth2", _scopes,
                    "jwt", _scopes 
                
            ;
            
        
    

用法:

        services.AddMySwagger(
            new ApiVersion(1, 0),
            __description => new Info  Title = $"API v__description.ApiVersion", Version = __description.ApiVersion.ToString() ,
            Configuration.GetValue<string>("Authentication:Authority"),
            new Dictionary<string, string>   Configuration.GetValue<string>("Authentication:Scope"), "Partnership API"  
        );

【讨论】:

【参考方案3】:

如果您在方法上指定了过滤器,Swagger 会添加授权标头。如果您在全球范围内需要授权,我的猜测是 swagger 无法识别它们。

您需要在 ConfigureServices 中添加这样的 SecurityRequirement:

c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>()

   "Bearer", new string[]  
);

如果设置了令牌,这将要求与 每个 请求一起发送标头。如果您在发送之前没有设置标头,但您的 api 描述旁边仍然会有挂锁标志。

【讨论】:

但是,当添加 AddSecurityRequirement 时,它会在 swagger UI 中显示的所有 APi 端点中添加挂锁,即使该 api 端点没有修饰 [Authorize] 属性。如何解决这个问题?感谢您的帮助。 @Vignesh AddSecurityRequirement 适用于所有 API 端点。如果您在方法上使用过滤器,Swagger 将添加挂锁。尝试使用代码示例创建您自己的问题,以便我们进一步帮助您。

以上是关于Swashbuckle .NET Core 2 中 JWT 承载的授权的主要内容,如果未能解决你的问题,请参考以下文章

.Net Core 2.0 中的 SwashBuckle UI 显示错误

是否可以在 ASP .NET Core 中使用 Swashbuckle 在 Swagger 2.0 和 Open API 3 格式中公开相同的 Swagger JSON?

asp.net core使用Swashbuckle.AspNetCore(swagger)生成接口文档

Swagger Swashbuckle Asp.NET Core:显示使用的每个枚举的详细信息

如何在 .NET Core 中实现处理自定义属性的 Swashbuckle IOperationFilter

在 swagger ui 中使用 asp.net core 3.1 中的 Swashbuckle 6.0.5 使 AuthorizationUrl 可配置