迁移到 Swashbuckle.AspNetCore 版本 5 时,Swagger UI 中的不记名身份验证

Posted

技术标签:

【中文标题】迁移到 Swashbuckle.AspNetCore 版本 5 时,Swagger UI 中的不记名身份验证【英文标题】:Bearer authentication in Swagger UI, when migrating to Swashbuckle.AspNetCore version 5 【发布时间】:2019-10-07 14:59:38 【问题描述】:

我正在尝试在 .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>() 

我认为那个位应该在某处有“Bearer”,但我不确定在哪里?

附加信息

这就是我首先设置 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
        ;
    );

【问题讨论】:

【参考方案1】:

OpenAPI 3.0 自带 Bearer 身份验证,这是一个安全方案,类型:http,scheme:bearer。

因此,您必须将安全方案类型设置为 HTTP 身份验证,然后按照RFC7235 中的定义定义 HTTP 授权方案的名称,而不是使用 API 密钥方案。 在本例中为“承载者”。

定义安全方案后,您可以通过将其添加为安全要求来应用它。

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

这省略了在令牌前面加上“Bearer”前缀的需要。

【讨论】:

请注意,执行此操作时,要粘贴到文本字段中的文本不再需要以字符串“Bearer”为前缀 @Daniel 谢谢你,我一直在尝试,同时仍然在前面加上“Bearer” 这是唯一一个可以使用 jwt 标准不记名令牌的。 @Pavlos 的回答让我走了。但在路上我有了一个奇怪的发现。如果字符串“Bearer”在任何地方都用大写字母 B 拼写,则它不起作用。但这必须是特定于版本的问题,因为我看到很多例子都用大写 B 拼写。我们使用的是 Swashbuckle.AspNetCore v5.4.1。【参考方案2】:

这些答案对我一路走来帮助很大。就我而言,我总是错过了另一件事——在使用[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));
    

【讨论】:

【参考方案3】:

最终通过反复试验得到了这个工作。这是对我有用的代码:

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

我怀疑那里可能设置了实际上不需要显式设置的属性,但以上内容对我有用。

【讨论】:

添加对 OpenApiSecurityScheme 的属性引用对我有用。谢谢! 天哪,真是一团糟......这是MS能想出的最好的...... 我们仍然需要在 swahbuckle ui 中添加单词“Bearer”+JWT。请参阅@Pavlos 的以下答案,以避免在使用 swagger 时重写“Bearer”关键字。

以上是关于迁移到 Swashbuckle.AspNetCore 版本 5 时,Swagger UI 中的不记名身份验证的主要内容,如果未能解决你的问题,请参考以下文章

如何从Eclipse迁移到IntelliJ IDEA

如何从Eclipse迁移到IntelliJ IDEA?

将 TFVC 迁移到 Git - 应该迁移历史记录吗?

如何把mariadb迁移到mysql

从 ASP.NET MVC 迁移到 ASP.NET Core MVC

从Oracle JDK迁移到OpenJDK:您需要了解的内容