web-dev-qa-db-ja.com

Swashbuckle.AspNetCoreバージョン5への移行

.NET Core 3 Preview 5 Web APIプロジェクトでSwashbuckleのバージョン4.0.1から5.0.0-rc2に移行しようとしています。

プロジェクトをコンパイルしてSwagger UIを機能させましたが、Bearer認証を機能させることができません。これは、新しい形式のセキュリティを正しく設定していないためと考えられます。

これは、バージョン4で機能した私の古いコードです。

c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
    Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
    Name = "Authorization",
    In = "header",
    Type = "apiKey"
});

var security = new Dictionary<string, IEnumerable<string>>
{
    {"Bearer", new string[] { }},
};

c.AddSecurityRequirement(security);

そしてこれは私がv5のためにそれを変更したものです:

c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
    Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
    Name = "Authorization",
    In = ParameterLocation.Header,
    Type = SecuritySchemeType.ApiKey,
    Scheme = "tomsAuth"
});

c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
    {
        new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference {
                Type = ReferenceType.SecurityScheme,
                Id = "tomsAuth" }
        }, new List<string>() }
});

私の問題はおそらくコードのこの部分にあると思います:

        new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference {
                Type = ReferenceType.SecurityScheme,
                Id = "tomsAuth" }
        }, new List<string>() }

そのビットはおそらくどこかに「ベアラー」が含まれているはずだと思いますが、どこにあるのかわかりませんか?

追加情報

これが、最初にJWT認証をセットアップする方法です。このコードは変更されておらず、Swashbuckle 4.0.1を使用していたときに機能していました。

    var appSettings = appSettingsSection.Get<AppSettings>();
    var key = Encoding.ASCII.GetBytes(appSettings.Secret);

    services.AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(x =>
    {
        x.Events = new JwtBearerEvents
        {
            OnTokenValidated = context =>
            {
                var userService = context.HttpContext.RequestServices.GetRequiredService<IApiUserService>();
                var userId = int.Parse(context.Principal.Identity.Name);
                var user = userService.GetById(userId);
                if (user == null)
                {
                    // return unauthorized if user no longer exists
                    context.Fail("Unauthorized");
                }

                return Task.CompletedTask;
            }
        };
        x.RequireHttpsMetadata = false;
        x.SaveToken = true;
        x.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    });
44
tomRedox

最終的には試行錯誤でこれを機能させました。これは私のために働くコードです:

c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
    Description =
        "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
    Name = "Authorization",
    In = ParameterLocation.Header,
    Type = SecuritySchemeType.ApiKey,
    Scheme = "Bearer"
});

c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
    {
        new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference
            {
                Type = ReferenceType.SecurityScheme,
                Id = "Bearer"
            },
            Scheme = "oauth2",
            Name = "Bearer",
            In = ParameterLocation.Header,

        },
        new List<string>()
    }
});

実際に明示的に設定する必要のないプロパティが設定されているのではないかと思いますが、上記ではうまくいきます。

96
tomRedox

OpenAPI 3.0には、ベアラー認証が付属しています。これは、タイプがhttpでスキームがベアラーのセキュリティスキームです。

したがって、APIキースキームを使用する代わりに、セキュリティスキームタイプをHTTP認証に設定し、次に RFC7235 で定義されているHTTP認可スキームの名前を定義する必要があります。この場合は「持参人」です。

セキュリティスキームを定義したら、セキュリティ要件として追加して適用できます。

//First we define the security scheme
c.AddSecurityDefinition("Bearer", //Name the security scheme
    new OpenApiSecurityScheme{
    Description = "JWT Authorization header using the Bearer scheme.",
    Type = SecuritySchemeType.Http, //We set the scheme type to http since we're using bearer authentication
    Scheme = "bearer" //The name of the HTTP Authorization scheme to be used in the Authorization header. In this case "bearer".
});

c.AddSecurityRequirement(new OpenApiSecurityRequirement{ 
    {
        new OpenApiSecurityScheme{
            Reference = new OpenApiReference{
                Id = "Bearer", //The name of the previously defined security scheme.
                Type = ReferenceType.SecurityScheme
            }
        },new List<string>()
    }
});
23
Pavlos

これらの答えは、道に沿って私を助けるのに素晴らしいものでした。私の場合、私は常にもう1つ欠けていました-SwaggerUIは、_[Authorize]_でアクション/コントローラーを装飾するときに、選択したヘッダー名/値(X-API-KEY)を認証ハンドラーに渡していませんでした。私のプロジェクトでは.NET Core 3.1とSwashbuckle 5を使用しています。以下の_Swashbuckle.AspNetCore.Filters_ nugetパッケージを使用してIOperationFilterを継承するカスタムクラスを作成し、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<ApiKeyOperationFilter>()です。

_// 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));
    }
}
_
1
Ben Sampica