Asp.NET Core 2.2: Swagger endpoint specific security definition

可紊 提交于 2021-02-08 05:37:22

问题


I am using Swashbuckle.AspNetCore 5.0.0-rc2 in one of my .Net Core 2.2 REST projects. Within my project I am serving two different apis which are logically connected to each other.

Today, I managed to seperate my swagger documentations to have one swagger endpoint per api containing only the corresponding api controllers.

I managed to do this by adding a specified group name to the api explorer settings of the controllers:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ApiExplorerSettings(GroupName = "contracts")]
public class ContractsController : BaseController

[Authorize(AuthenticationSchemes = "BasicAuthentication")]
[ApiExplorerSettings(GroupName = "clearing")]
public class ClearingController : BaseController

With that settings I was able to specify to different endpoints for swagger within my Startup.cs

  // Enable documentation middleware
  app.UseSwagger(so =>
  {
    so.RouteTemplate = "api/doc/{documentName}/swagger.json";
  });
  app.UseSwaggerUI(suo =>
  {
    suo.SwaggerEndpoint("/api/doc/contracts/swagger.json", "Contracts API");
    suo.SwaggerEndpoint("/api/doc/clearing/swagger.json", "Clearing API");
    suo.RoutePrefix = "api/doc";
    suo.SupportedSubmitMethods(SubmitMethod.Get, SubmitMethod.Post, SubmitMethod.Patch, SubmitMethod.Delete);
  });

That worked and everything was fine.

Now as you probably noticed, I am using different authorization methods for the controllers of each api. The first, the contracts api, is using a JWT Token authorization, while the second one, the clearing api is using a Basic authorization.

I thought, the swagger ui would automatically use the correct Authorization method by the "Authorize" Attribute, but I was wrong.

Well I added both authorization methods to the swagger ui middleware like this:

  options.AddSecurityDefinition("Bearer", GetSwaggerTokenSecurityScheme());
  options.AddSecurityDefinition("Basic", GetSwaggerBasicSecurityScheme());

  options.AddSecurityRequirement(GetSwaggerJwtSecurityRequirement());
  options.AddSecurityRequirement(GetSwaggerBasicSecurityRequirement());

Heres my full swagger configuration code:

/// <summary>
/// Configures the swagger generation
/// </summary>
/// <param name="config">The swagger configuration</param>
/// <param name="options">The swagger gen options instance</param>
public static void ConfigureSwaggerGen(IConfiguration config, SwaggerGenOptions options)
{
  var swaggerConfig = config.Get<SwaggerConfiguration>();
  AddSwaggerDocPerApiType(swaggerConfig, options);

  options.AddSecurityDefinition("Bearer", GetSwaggerTokenSecurityScheme());
  options.AddSecurityDefinition("Basic", GetSwaggerBasicSecurityScheme());

  options.AddSecurityRequirement(GetSwaggerJwtSecurityRequirement());
  options.AddSecurityRequirement(GetSwaggerBasicSecurityRequirement());

  if (!swaggerConfig.SwaggerIncludeXml)
  {
    return;
  }
  var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
  xmlFiles.ToList().ForEach(f => options.IncludeXmlComments(f));
  options.DescribeAllEnumsAsStrings();
}

/// <summary>
/// Adds a swagger documentation for each api type
/// </summary>
/// <param name="config">The swagger configuration</param>
/// <param name="options">The swagger gen options instance</param>
private static void AddSwaggerDocPerApiType(SwaggerConfiguration config, SwaggerGenOptions options)
{
  options.SwaggerDoc("contracts", GetSwaggerInformationParams(config, "Contracts"));
  options.SwaggerDoc("clearing", GetSwaggerInformationParams(config, "Clearing"));
}

/// <summary>
/// Generates swagger information params object
/// according to the given configuration
/// </summary>
/// <param name="config">The configuration</param>
/// <param name="apiType">The api type</param>
/// <returns>The swagger information</returns>
private static OpenApiInfo GetSwaggerInformationParams(SwaggerConfiguration config, string apiType = "")
{
  var title = string.IsNullOrEmpty(apiType) ? config.SwaggerTitle : apiType;
  var version = string.IsNullOrEmpty(apiType) ? Assembly.GetExecutingAssembly().GetName().Version.ToString() : apiType;

  var swaggerInfo = new OpenApiInfo()
  {
    Title = title,
    Version = version.ToLower(),
    Description = config.SwaggerDescription,
    Contact = new OpenApiContact()
    {
      Name = config.SwaggerCompany,
      Email = config.SwaggerContactMail,
      Url = new Uri(config.SwaggerContactUrl)
    }
  };
  return swaggerInfo;
}

/// <summary>
/// Generates the swagger jwt security scheme object
/// </summary>
/// <returns>The swagger jwt security scheme</returns>
private static OpenApiSecurityScheme GetSwaggerTokenSecurityScheme()
{
  var scheme = new OpenApiSecurityScheme
  {
    Description = "JWT authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
    Name = "JwtAuthorization",
    In = ParameterLocation.Header,
    Type = SecuritySchemeType.ApiKey
  };
  return scheme;
}

/// <summary>
/// Generates the swagger basic security scheme object
/// </summary>
/// <returns>The swagger basic security scheme</returns>
private static OpenApiSecurityScheme GetSwaggerBasicSecurityScheme()
{
  var scheme = new OpenApiSecurityScheme
  {
    Description = "Basic authorization header. Example: \"Authorization: username:password\"",
    Name = "BasicAuthorization",
    In = ParameterLocation.Header,
    Type = SecuritySchemeType.Http,
    Scheme = "basic"
  };
  return scheme;
}

/// <summary>
/// Generates the swagger security scheme object
/// </summary>
/// <returns>The swagger security scheme</returns>
private static OpenApiSecurityRequirement GetSwaggerJwtSecurityRequirement()
{
  var req = new OpenApiSecurityRequirement()
  {
    {
      new OpenApiSecurityScheme()
      {
        Reference = new OpenApiReference() {Type = ReferenceType.SecurityScheme, Id = "Bearer"}
      },
      new[] {"readAccess", "writeAccess"}
    }
  };
  return req;
}

/// <summary>
/// Generates the swagger security scheme object
/// </summary>
/// <returns>The swagger security scheme</returns>
private static OpenApiSecurityRequirement GetSwaggerBasicSecurityRequirement()
{
  var req = new OpenApiSecurityRequirement()
  {
    {
      new OpenApiSecurityScheme()
      {
        Reference = new OpenApiReference() {Type = ReferenceType.SecurityScheme, Id = "Basic"}
      },
      new[] {"readAccess", "writeAccess"}
    }
  };
  return req;
}

Now what I want to achieve is, that only the JWT token authorization is available for the contracts api controllers and only the basic authorization is available for the clearing api controllers.

At the moment I always have both authorization methods available for any api:

Does anybody know how to specify the security for the specific documentation endpoint only?

Best regards


回答1:


The SwaggerGenOptions.AddSecurityRequirement will apply the Security Requirement globally, so that the security icon (lock icon) and authentication inputs will be applied to all APIs.

Here are the workable solution for me to ONLY apply Security Requirement on protected APIs.

  • Remove SwaggerGenOptions.AddSecurityRequirement from global settings.
  • Create a custom OperationFilter, that implements Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter, and only add SecurityRequirement on protected APIs.
public class AuthorizationOperationFilter : IOperationFilter
{
   public void Apply(OpenApiOperation operation, OperationFilterContext context)
   {
       // Get Authorize attribute
       var attributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
                               .Union(context.MethodInfo.GetCustomAttributes(true))
                               .OfType<AuthorizeAttribute>();

       if (attributes != null && attributes.Count() > 0)
       {
          var attr = attributes.ToList()[0];

          // Add what should be show inside the security section
          IList<string> securityInfos = new List<string>();
          securityInfos.Add($"{nameof(AuthorizeAttribute.Policy)}:{attr.Policy}");
          securityInfos.Add($"{nameof(AuthorizeAttribute.Roles)}:{attr.Roles}");
          securityInfos.Add($"{nameof(AuthorizeAttribute.AuthenticationSchemes)}:{attr.AuthenticationSchemes}");

          switch (attr.AuthenticationSchemes)
          {
               case var p when p == AuthenticationScheme.Basic:
                   operation.Security = new List<OpenApiSecurityRequirement>()
                   {
                        new OpenApiSecurityRequirement()
                        {
                            {
                                new OpenApiSecurityScheme
                                {
                                    Reference = new OpenApiReference
                                    {
                                        Id = "basic", // Must fit the defined Id of SecurityDefinition in global configuration
                                        Type = ReferenceType.SecurityScheme,
                                    }
                                },
                                securityInfos
                            }
                        }
                    };
                    break;

                case var p when p == AuthenticationScheme.Bearer: // = JwtBearerDefaults.AuthenticationScheme
                default:
                    operation.Security = new List<OpenApiSecurityRequirement>()
                    {
                        new OpenApiSecurityRequirement()
                        {
                            {
                                new OpenApiSecurityScheme
                                {
                                    Reference = new OpenApiReference
                                    {
                                        Id = "bearer", // Must fit the defined Id of SecurityDefinition in global configuration
                                        Type = ReferenceType.SecurityScheme
                                    }
                                },
                                securityInfos
                            }
                        }
                    };
                    break;
            }
        }
        else
        {
            operation.Security.Clear();
        }
    }
}

Then enable the custom OperationFilter when configuring SwaggerGenOptions:

services.AddSwaggerGen(c =>
{
    
     // Set the custom operation filter
     c.OperationFilter<AuthorizationOperationFilter>();
     
     // Add JWT Authentication
     var securityScheme = new OpenApiSecurityScheme
     {
         Name = "JWT Authentication",
         Description = "Enter JWT Bearer token **_only_**",
         In = ParameterLocation.Header,
         Type = SecuritySchemeType.Http,
         Scheme = "bearer",
         BearerFormat = "JWT",
         Reference = new OpenApiReference
         {
             Id = "bearer",
             Type = ReferenceType.SecurityScheme
         }
     };
     c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme);

    
     // Add Basic Authentication
     var basicSecurityScheme = new OpenApiSecurityScheme
     {
         Name = "Basic Authentication",
         Type = SecuritySchemeType.Http,
         Scheme = "basic",
         Reference = new OpenApiReference 
         { 
              Id = "basic", 
              Type = ReferenceType.SecurityScheme 
         }
     };
     c.AddSecurityDefinition(basicSecurityScheme.Reference.Id, basicSecurityScheme);
});

Please refer to my article and sample code for more details.




回答2:


based on the information here:

  1. https://github.com/domaindrivendev/Swashbuckle.AspNetCore#add-security-definitions-and-requirements

  2. https://swagger.io/docs/specification/authentication/

you need to remove the global security requirement added by the statement 'options.AddSecurityRequirement'

and replace it, with security operation defined by the 'options.AddSecurityDefinition' and bind it to the Authorize statement applied to the operations

the link at swagger.io demonstrates various pattens for the security being demanded

hope that helps

+RD



来源:https://stackoverflow.com/questions/58694220/asp-net-core-2-2-swagger-endpoint-specific-security-definition

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!