迁移到 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<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));
【讨论】:
【参考方案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 中的不记名身份验证的主要内容,如果未能解决你的问题,请参考以下文章