这需要很长时间才能弄清楚,我相信还有更好的方法。但这里是我添加这个的步骤:
首先在WebAPI Startup中,需要正确配置Swagger:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "My Cool Tool",
Version = "v1",
Description = "My Cool API",
});
//add jwt authentication definition to the OpenAPI doc.
var securityScheme = new OpenApiSecurityScheme
{
Name = "JWT Authentication",
Description = "Enter JWT Bearer Token Only",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme
}
};
c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme);
//to indicate the entire API is secured, add this. NOTE it does NOT secure it, just indicates it is.
//c.AddSecurityRequirement(new OpenApiSecurityRequirement
//{
// {securityScheme, new string[]{ } }
//});
//this filter does add if an API is secured based upon the Authorize attribute
c.OperationFilter<SecurityRequirementsOperationFilter>();
});
注意最后几行是关键,AddSecurityDefinition 在末尾放置一个块,详细说明您使用的方案。如果您取消注释这些行,则安全方案将添加到每个操作(不太可能是您想要的!),所以最后一行只是将其添加到带有 [Authorize] 标记的那些操作/方法中。
而那个SecurityRequirementsOperationFilter如下
public class SecurityRequirementsOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
//if the controller method has the authorize attribute, get the roles
var requiredRoles = context.MethodInfo
.GetCustomAttributes(true)
.OfType<AuthorizeAttribute>()
.Select(attr => attr.Roles)
.Distinct();
if (requiredRoles.Any())
{
//add the fact that this operation could return the 401/403 HTTP status codes
operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
//add the fact that this operation is secured by bearer auth
var bearerScheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme
}
};
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
{ bearerScheme, requiredRoles.ToList() }
}
};
}
}
}
您可以跳过角色部分,只需将ToList 替换为空字符串数组即可。
最后,我们在 Startup 中进行了一些更标准的授权设置:
//get token management settings
var token = Configuration.GetSection("tokenManagement").Get<TokenManagement>();
services.AddSingleton(token);
//add authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = token.Issuer,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidAudience = token.Audience,
ValidateAudience = false
};
});
//add the authentication user service
services.AddScoped<IUserService, UserService>();
TokenManagement 是一个 poco,用于存储令牌的一些配置项。
UserService 是您拥有 IsValidUser 的类,您可以在其中添加实际支票。
最后你需要某种登录方法:
[AllowAnonymous]
[HttpPost("login")]
[ProducesResponseType(typeof(LoginResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult Login([FromBody] LoginRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest("Invalid Request");
}
if (!_userService.IsValidUser(request.UserName, request.Password))
{
return BadRequest("Invalid Request");
}
var claims = new[]
{
new Claim(ClaimTypes.Name, request.UserName),
new Claim(ClaimTypes.Role, _userService.GetUserRole(request.UserName))
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenManagement.Secret));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwtToken = new JwtSecurityToken(
_tokenManagement.Issuer,
_tokenManagement.Audience,
claims,
expires: DateTime.Now.AddMinutes(_tokenManagement.AccessExpiration),
signingCredentials: credentials);
var token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
_logger.LogInformation($"User [{request.UserName}] logged in the system.");
return Ok(new LoginResult
{
UserName = request.UserName,
JwtToken = token
});
}
此时您可以使用[Authorize] 属性来装饰您的api 方法,并且后续调用应该在请求中没有Bearer 标头的情况下被阻止。
对于客户端,NSwag 将为客户端生成一个 partial 类,这是我扩展它的地方,并为提供的 PrepareRequest 方法之一提供了一个主体,因为它在 HttpClient 之前被调用Send.
partial void PrepareRequest(HttpClient client, HttpRequestMessage request, string url)
{
_logger.LogInformation($"Prepare Request: {request.RequestUri.AbsoluteUri}");
var user = _storage.GetItem<LoginResult>("user");
if (user != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", user.JwtToken);
_logger.LogDebug($"Headers: {request.Headers.ToString()}");
}
}
这里的关键是这个方法必须是同步的,因为生成的客户端不await它。
所以就我而言,我使用Blazored.LocalStorage 包来获取这些同步方法。请注意,因为我需要LocalStorageService,所以我还必须在部分类文件中为客户端添加我自己的构造函数,该构造函数调用了 NSwag 生成的构造函数。
有了这个,你现在可以添加一个登录页面来调用之前的登录方法,存储令牌,现在当你调用 API 时,你的不记名令牌将被添加。