简体   繁体   English

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

[英]Authorization for JWT bearer in Swashbuckle .NET Core 2

I use tokens generated by an authentication service for my app.我将身份验证服务生成的令牌用于我的应用程序。 No problems there.没有问题。 Now I have introduced Swashbuckle to document my API an I can authenticate as follows by sending the JWT with every request using this code;现在我已经引入了 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"
            });
        }
    }
}

Which gives me the following header - as expected这给了我以下标题 - 正如预期的那样

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

Although, I am not sure where the Cookie is coming from.虽然,我不确定 Cookie 来自哪里。 That is nothing to do with my code.这与我的代码无关。 I just ignore it - so far so good.我只是忽略它 - 到目前为止一切顺利。

The problem is that this means the token has to be entered with every request which is a pain.问题是这意味着每个请求都必须输入令牌,这很痛苦。 Ideally, I would want to authenticate using the inbuilt swagger interface - according to several articles, I should be able to do this;理想情况下,我希望使用内置的 swagger 界面进行身份验证 - 根据几篇文章,我应该能够做到这一点;

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

This works fine, and I can add the token, there just seems to be a step I am missing to add the token to the header of every request.这工作正常,我可以添加令牌,但似乎缺少将令牌添加到每个请求的标头的步骤。 If I just add the auth, then this gives me the following header, which of course fails the authentication.如果我只是添加身份验证,那么这会给我以下标头,这当然会导致身份验证失败。

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

What else do I need to do in order to get the request to include the token for every subsequent request?为了让请求包含每个后续请求的令牌,我还需要做什么?

Swagger would add the authorzation header if you specified the filter on your methods.如果您在方法上指定了过滤器,Swagger 会添加授权标头。 If you globally require authorization my guess is that swagger doesn't recognize them.如果您在全球范围内需要授权,我的猜测是 swagger 无法识别它们。

You need to add a SecurityRequirement like this in your ConfigureServices:您需要在 ConfigureServices 中添加这样的 SecurityRequirement:

c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>()
{
  { "Bearer", new string[]{ } }
});

This will require the header to be sent with every request if the token is set.如果设置了令牌,这将要求与每个请求一起发送标头。 If you didn't set the header before it'll not send it, but you'll still have the padlock sign next to your api description.如果您在它不会发送之前没有设置标头,但您的 api 描述旁边仍然会有挂锁标志。

If you define in code如果你在代码中定义

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

and then use it in not in .Parameters, but in .Security然后在 .Parameters 中而不是在 .Security 中使用它

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

then everything should work:那么一切都应该有效:

I do the same as you do, but you should add like follows (for oauth2 or jwt bearer token auth):我和你做的一样,但你应该添加如下(对于 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 }
                }
            };
            }
        }
    }
}

Usage:用法:

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

In the end I moved to NSwag so I am not sure what the original issue was.最后我搬到了 NSwag,所以我不确定最初的问题是什么。 The solution in Nswag is as follows; 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"));
        });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM