【问题标题】:Swagger client generation with api key authentication使用 api 密钥身份验证生成 Swagger 客户端
【发布时间】:2017-10-24 23:24:57
【问题描述】:

我有一个现有的 C# ASP.NET Web API 2 项目 (.NET 4.6.1),我需要在其中集成 Swagger 以生成文档以及客户端 SDK(目前只有 C#)。就像在其最新版本 (5.5.3) 中使用 Swashbuckle 一样。

除了一件事,一切都很顺利。我遇到的问题是我的 SwaggerConfig.cs 中定义的安全性(通过 HTTP 标头的 apiKey)最终出现在输出 JSON 中,但不知何故,它没有链接到任何方法(即使它是强制性的)。

我的安全配置定义如下:

GlobalConfiguration.Configuration.EnableSwagger(c =>
{
    c.SingleApiVersion("v1", "Dummy API")
    c.ApiKey("apiKey")
        .Description("API Key Authentication")
        .Name("X-API-Key")
        .In("header");
}).EnableSwaggerUi(c =>
{
    c.EnableApiKeySupport("X-API-Key", "header");
});

以及生成的 Swagger JSON 中的结果:

"securityDefinitions": {
        "apiKey": {
        "type": "apiKey",
        "description": "API Key Authentication",
        "name": "X-API-Key",
        "in": "header"
    }
}

这是我得到的:

"/api/ping": {
  "get": {
    "tags": [
      "Dummy"
    ],
    "summary": "Ping.",
    "operationId": "ping",
    "consumes": [],
    "produces": [
      "application/json"
    ],
    "responses": {
      "200": {
        "description": "OK",
        "schema": {
          "type": "string"
        }
      }
    }
  }
}

与我想要获得的相比:

"/api/ping": {
  "get": {
    "tags": [
      "Dummy"
    ],
    "summary": "Ping.",
    "operationId": "ping",
    "consumes": [],
    "produces": [
      "application/json"
    ],
    "responses": {
      "200": {
        "description": "OK",
        "schema": {
          "type": "string"
        }
      }
    },
    "security": [
      {
        "apiKey": []
      }
    ]
  }
}

知道我应该在项目中更改什么以便生成security 部分吗?

【问题讨论】:

  • 您介意注意您引用的版本吗?
  • 我已经编辑了我的原始帖子,我正在使用最新版本的 Swashbuckle (5.5.3)

标签: c# json asp.net-web-api2 swagger api-key


【解决方案1】:

这个问题和其他问题对我一路走来帮助很大。就我而言,我总是错过了另一件事——在使用[Authorize] 装饰动作/控制器时,SwaggerUI 没有将我选择的标头名称/值 (X-API-KEY) 传递给我的身份验证处理程序。我的项目使用 .NET Core 3.1 和 Swashbuckle 5。我创建了一个继承 IOperationFilter 的自定义类,它使用下面的 Swashbuckle.AspNetCore.Filters nuget 包来搭载他们对 oauth2 的实现。

// Startup.cs
// ...
services.AddSwaggerGen(options =>
{
  options.SwaggerDoc("v1", new OpenApiInfo { Title = nameof(BoardMinutes), Version = "v1" });

  // Adds authentication to the generated json which is also picked up by swagger.
  options.AddSecurityDefinition(ApiKeyAuthenticationOptions.DefaultScheme, new OpenApiSecurityScheme
  {
      In = ParameterLocation.Header,
      Name = ApiKeyAuthenticationHandler.ApiKeyHeaderName,
      Type = SecuritySchemeType.ApiKey
  });

  options.OperationFilter<ApiKeyOperationFilter>();
});

关键组件是options.AddSecurityDefinition()(我有一些开放的端点,不想提供全局过滤器)以及options.OperationFilter&lt;ApiKeyOperationFilter&gt;()

// ApiKeyOperationFilter.cs
// ...
internal class ApiKeyOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        // Piggy back off of SecurityRequirementsOperationFilter from Swashbuckle.AspNetCore.Filters which has oauth2 as the default security scheme.
        var filter = new SecurityRequirementsOperationFilter(securitySchemaName: ApiKeyAuthenticationOptions.DefaultScheme);
        filter.Apply(operation, context);
    }
}

最后 - 对于完整的图片,这里是身份验证处理程序和身份验证选项

// ApiKeyAuthenticationOptions.cs
// ... 
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
    public const string DefaultScheme = "API Key";
    public string Scheme => DefaultScheme;
    public string AuthenticationType = DefaultScheme;
}

// ApiKeyAuthenticationHandler.cs
// ...
internal class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
    private const string ProblemDetailsContentType = "application/problem+json";
    public const string ApiKeyHeaderName = "X-Api-Key";

    private readonly IApiKeyService _apiKeyService;
    private readonly ProblemDetailsFactory _problemDetailsFactory;

    public ApiKeyAuthenticationHandler(
        IOptionsMonitor<ApiKeyAuthenticationOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock,
        IApiKeyService apiKeyService,
        ProblemDetailsFactory problemDetailsFactory) : base(options, logger, encoder, clock)
    {
        _apiKeyService = apiKeyService ?? throw new ArgumentNullException(nameof(apiKeyService));
        _problemDetailsFactory = problemDetailsFactory ?? throw new ArgumentNullException(nameof(problemDetailsFactory));
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKeyHeaderValues))
        {
            return AuthenticateResult.NoResult();
        }

        Guid.TryParse(apiKeyHeaderValues.FirstOrDefault(), out var apiKey);

        if (apiKeyHeaderValues.Count == 0 || apiKey == Guid.Empty)
        {
            return AuthenticateResult.NoResult();
        }

        var existingApiKey = await _apiKeyService.FindApiKeyAsync(apiKey);

        if (existingApiKey == null)
        {
            return AuthenticateResult.Fail("Invalid API Key provided.");
        }

        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, existingApiKey.Owner)
        };

        var identity = new ClaimsIdentity(claims, Options.AuthenticationType);
        var identities = new List<ClaimsIdentity> { identity };
        var principal = new ClaimsPrincipal(identities);
        var ticket = new AuthenticationTicket(principal, Options.Scheme);

        return AuthenticateResult.Success(ticket);
    }

    protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        Response.StatusCode = StatusCodes.Status401Unauthorized;
        Response.ContentType = ProblemDetailsContentType;
        var problemDetails = _problemDetailsFactory.CreateProblemDetails(Request.HttpContext, StatusCodes.Status401Unauthorized, nameof(HttpStatusCode.Unauthorized),
            detail: "Bad API key.");

        await Response.WriteAsync(JsonSerializer.Serialize(problemDetails));
    }

    protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
    {
        Response.StatusCode = StatusCodes.Status403Forbidden;
        Response.ContentType = ProblemDetailsContentType;
        var problemDetails = _problemDetailsFactory.CreateProblemDetails(Request.HttpContext, StatusCodes.Status403Forbidden, nameof(HttpStatusCode.Forbidden),
            detail: "This API Key cannot access this resource.");

        await Response.WriteAsync(JsonSerializer.Serialize(problemDetails));
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-31
    • 1970-01-01
    • 2023-03-19
    • 2018-09-16
    相关资源
    最近更新 更多