【问题标题】:Asp.NET Core 2.2: Swagger endpoint specific security definitionAsp.NET Core 2.2:Swagger 端点特定的安全定义
【发布时间】:2020-02-29 19:35:30
【问题描述】:

我在我的一个 .Net Core 2.2 REST 项目中使用 Swashbuckle.AspNetCore 5.0.0-rc2。在我的项目中,我提供了两个在逻辑上相互连接的不同 api。

今天,我设法将我的 swagger 文档分开,使每个 api 有一个 swagger 端点,其中仅包含相应的 api 控制器。

我设法通过在控制器的 api explorer 设置中添加一个指定的组名来做到这一点:

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

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

通过这些设置,我可以在我的 Startup.cs 中为 swagger 指定不同的端点

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

效果很好,一切都很好。

现在您可能已经注意到,我为每个 api 的控制器使用不同的授权方法。第一个,contracts api,使用的是 JWT Token 授权,而第二个,clearing api 使用的是 Basic 授权。

我以为,swagger ui会通过“Authorize”属性自动使用正确的授权方法,但我错了。

我在 swagger ui 中间件中添加了两种授权方法,如下所示:

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

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

这是我完整的 swagger 配置代码:

/// <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;
}

现在我想要实现的是,只有 JWT 令牌授权可用于合约 api 控制器,而只有基本授权可用于清算 api 控制器。

目前我总是有两种授权方法可用于任何 api:

有人知道如何仅指定特定文档端点的安全性吗?

最好的问候

【问题讨论】:

  • 据我了解,您必须根据 GetSwaggerTokenSecurityScheme 或 GetSwaggerBasicSecurityScheme 中的控制器名称跳过授权。
  • 我用来添加安全定义的方式对于 swagger 文档来说是全局性的。 :/ 我猜我无法根据 api 跳过它们。 ://
  • 您找到解决方案了吗? @epanalepsis
  • 很遗憾没有,这就是我将文档分开的原因。

标签: c# swagger swagger-ui asp.net-core-2.2


【解决方案1】:

SwaggerGenOptions.AddSecurityRequirement 将在全局范围内应用安全要求,以便将安全图标(锁图标)和身份验证输入应用到所有 API。

这是我仅在受保护的 API 上应用安全要求的可行解决方案。

  • 从全局设置中删除 SwaggerGenOptions.AddSecurityRequirement
  • 创建一个自定义 OperationFilter,实现 Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter,并且只在受保护的 API 上添加 SecurityRequirement
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();
        }
    }
}

然后在配置SwaggerGenOptions时启用自定义OperationFilter:

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

更多详情请参考我的articlesample code

【讨论】:

    【解决方案2】:

    根据此处的信息:

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

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

    您需要删除语句'options.AddSecurityRequirement'添加的全局安全要求

    并将其替换为“options.AddSecurityDefinition”定义的安全操作,并将其绑定到应用于操作的授权语句

    swagger.io 上的链接演示了所需安全性的各种模式

    希望对你有帮助

    +研发

    【讨论】:

      猜你喜欢
      • 2018-11-11
      • 2022-11-14
      • 2018-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-06
      • 1970-01-01
      • 2018-10-16
      相关资源
      最近更新 更多