当迁移到 Swashbuckle 时,在 Swagger UI 中进行承载者身份验证

我正在尝试从版本4.0.1迁移到5.0.0-rc2。NET 核心3预览5 Web API 项目。

我已经完成了项目编译和 Swagger 用户界面的工作,但是我无法让 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);

这是我在第5版中改成的:

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 身份验证的方式。这段代码没有改变,而且在我使用 Swashuckle 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
};
});
63689 次浏览

Got this working in the end by trial and error. This is the code that works for me:

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

I suspect there are probably properties being set there that don't actually need to be explicitly set, but the above is working for me.

OpenAPI 3.0 comes with Bearer authentication, which is a security scheme with type: http and scheme: bearer.

So instead of using an API key scheme you have to set the security scheme type to HTTP Authentication and then define the name of the HTTP Authorization scheme as defined in RFC7235. In this case "bearer".

After you've defined the security scheme you can apply it by adding it as a security requirement.

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

This omits the need to prefix the token with "Bearer ".

These answers were great in helping me along the path. In my case, I was always just missing one more thing - the SwaggerUI wasn't passing the header name/value I chose (X-API-KEY) to my authentication handler when decorating actions/controllers with [Authorize]. My project uses .NET Core 3.1 and Swashbuckle 5. I made a custom class that inherits IOperationFilter that uses the Swashbuckle.AspNetCore.Filters nuget package below to piggyback off their implementation for 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>();
});

The key components are the options.AddSecurityDefinition() (I have some open endpoints and didn't want to provide a global filter) as well as 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);
}
}

And finally - for the complete picture here's the authentication handler and authentication options

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

After finding a solution, I created an example Swagger-UI ASP.NET 6 application where the bearer token is requested form the authentication provider when you click the Authorize button. See https://github.com/inouiw/SwaggerUIJsonWebToken