c# - 根据用户配置 JwtBearerOptions IssuerSigningKey
问题描述
我开始使用 Jwt 在 NET Core 中进行 API 身份验证,并且像往常一样,我阅读了一些示例和教程,我注意到的一件事是,它们中的大多数都SymmetricSecutityKey
基于存储在某处的已知字符串生成(无论是文件还是硬编码)或随机输出。我设法使身份验证正常工作,但现在我坚持以下几点:如何设置StartUp.cs
配置以便验证IssueSigningKey
参数检查多个密钥?贝娄,一段工作代码:
认证控制器
var authClaims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.Ticks.ToString())
};
var ssKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("7S79jvOkEdwoRqHx"));
var securityToken = new JwtSecurityToken(
issuer: _apiSettings.BearerValidIssuer,
audience: _apiSettings.BearerValidAudience,
expires: DateTime.Now.AddHours(6),
claims: authClaims,
signingCredentials: new SigningCredentials(ssKey, SecurityAlgorithms.HmacSha256Signature)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(securityToken),
expiration = securityToken.ValidTo,
});
而当前的StartUp.cs
配置,关于承载令牌:
启动.cs
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = _apiSettings.GetValue(typeof(string), "BearerValidIssuer").ToString(),
ValidateAudience = true,
ValidAudience = _apiSettings.GetValue(typeof(string), "BearerValidAudience").ToString(),
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("7S79jvOkEdwoRqHx")),
};
});
我知道,至少为了举例,将生成对称密钥的字符串存储在文件(例如 .json 配置文件)的某个位置是常见的做法,但我想生成它并存储它们与用户信息一起在数据库中。该密钥将在某个时候传递给用户,然后它将用于通过 REST 请求生成访问令牌。这是可以实现的吗?还添加如果这在安全性方面甚至是实用的,或者我可以“只是”在文件中记录字符串?
解决方案
好吧,为了给有同样疑问的人留下参考,这就是我解决问题的方法。
首先,我必须修改我的身份验证控制器,以便JwtSecurityToken
根据每个用户存储在数据库中的字符串创建签名凭据。该字符串是从请求的“授权”标头中检索的,并将与用户一起保存。这是你证明“你就是你”的部分。此授权令牌还有两个特定的声明,一个用于“WebApi”访问的角色和用户“uid”。
授权控制器
public async Task<IActionResult> GetAuthenticationToken([FromBody] JSONBody json)
{
try
{
User user = await _userManager.FindByEmailAsync(json.Email);
if (user != null)
{
if (await _userManager.IsInRoleAsync(user, "WebApi"))
{
if (HttpContext.Request.Headers.TryGetValue("Authorization", out StringValues headerValues))
{
string sskeyString = headerValues.First();
if (user.SymmetricSecurityKeyString == sskeyString)
{
var authClaims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.Ticks.ToString()),
new Claim(ClaimTypes.Role, "WebApi"),
new Claim("uid", user.Id)
};
var ssKey = new SymmetricSecurityKey(Convert.FromBase64String(user.SymmetricSecurityKeyString));
var securityToken = new JwtSecurityToken(
issuer: _apiSettings.BearerValidIssuer,
audience: _apiSettings.BearerValidAudience,
expires: DateTime.Now.AddHours(6),
claims: authClaims,
signingCredentials: new SigningCredentials(ssKey, SecurityAlgorithms.HmacSha256Signature)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(securityToken),
expiration = securityToken.ValidTo,
});
}
else
return Unauthorized(new { Message = "Token de authenticação Inválido." });
}
else
return Unauthorized(new { Message = "Header Authorization não encontrado." });
}
else
return Unauthorized(new { Message = "Este usuário não possui acesso à WebApi. Contate a DescontaNet para solicitar este perfil de acesso." });
}
else
return NotFound(new { Message = $"Usuário não encontrado para este email: {json.Email}" });
}
catch (Exception ex)
{
return StatusCode(500, ex.InnerException);
}
}
在阅读了有关该AddAuthentication().AddJwtBearer()
方法的更多信息后,我了解到您可以设置多个身份验证方案,每个方案都有自己的选项。最重要的选项是SecurityTokenValidators
属性,它是一个IList<ISecurityTokenValidators>
. 这将验证身份验证期间发送到 API 的任何令牌。所以StartUp.cs
现在看起来像这样:
启动.cs
services.AddAuthentication()
.AddJwtBearer("apiToken", options => {
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = _apiSettings.GetValue(typeof(string), "BearerValidIssuer").ToString(),
ValidateAudience = true,
ValidAudience = _apiSettings.GetValue(typeof(string), "BearerValidAudience").ToString(),
ValidateIssuerSigningKey = true,
};
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(new DynamicKeyJwtValidationHandler(Configuration.GetConnectionString("DNDrive")));
});
// There is also a policy to require this specific token validation in certain methods
services.AddAuthorization(options => {
options.AddPolicy("WebApiPolicy", policy => {
policy.RequireRole("WebApi");
policy.RequireAuthenticatedUser();
policy.AddAuthenticationSchemes("apiToken");
});
一个次要但重要的细节:调用services.SecurityTokenValidators.Clear()
可确保删除所有默认方案,并且在身份验证期间仅检查您的方案。说到这,我不得不自己查找并制作一个验证器,如下所示:
DynamicKeyJwtTokenValidationHandler 类
public class DynamicKeyJwtValidationHandler : JwtSecurityTokenHandler, ISecurityTokenValidator
{
private DNDriveContext db;
public DynamicKeyJwtValidationHandler(string connectionStr)
{
var optionsBuilder = new DbContextOptionsBuilder<DNDriveContext>();
optionsBuilder.UseSqlServer(connectionStr);
db = new DNDriveContext(optionsBuilder.Options);
}
private SecurityKey GetSSKeyForId(string id)
{
var user = db.User.Where(u => u.Id == id).FirstOrDefault();
if (user == null)
throw new Exception("User Id not found");
return new SymmetricSecurityKey(Convert.FromBase64String(user.SymmetricSecurityKeyString));
}
public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
// Read the token before starting validation
JwtSecurityToken incomingToken = ReadJwtToken(token);
// Extract external system ID from the token
string externalSystemId = incomingToken.Claims.First(claim => claim.Type == "uid").Value;
// Retrieve the Symmetric Security Key String from the database
SecurityKey publicKeyForExternalSystem = GetSSKeyForId(externalSystemId);
// Set up the Security Key for that user
validationParameters.IssuerSigningKey = publicKeyForExternalSystem;
// Framework default validation
return base.ValidateToken(token, validationParameters, out validatedToken);
}
}
该类继承自JwtSecurityTokenHandler
进行验证并实现ISecurityTokenValidator
接口。在我的例子中,每个用户都有一个单独的令牌来验证是很重要的,因此我访问数据库并SymmetricSecurityKey
从存储在数据库中的字符串中检索一个。如何在数据库中搜索用户?此令牌具有Claim
“uid”类型的自定义。使用SecurityKey
它应该进行验证,我调用该base.ValidateToken()
方法,传递传入的令牌并返回结果。
有了这个,以及在 中创建的策略StartUp.cs
,我可以轻松地验证具有此标记的控制器下的任何方法的令牌[Authorize(Policy = "WebApiPolicy")]
:可能有一些方法可以提高安全性,因为最终用户仍然必须保证他的身份验证密钥的安全,但至少我设法添加了额外的验证层,并且 JWT 令牌有一个到期日期。
推荐阅读
- javascript - UI 上的聊天助手图标需要滚动(从左到右)效果
- python - 冒泡/合并排序算法类型:需要更正证明
- c++ - Error-addresssanitizer-negative-size-param-size-(-4)
- linux - 从 Rundeck (Linux) 运行命令的问题
- android - 关于 fresco 的 Launch Crash
- dynamics-crm - 如何定义实体属性的选择列表是全局的还是局部的?
- spring - java.lang.NoClassDefFoundError: net/bytebuddy/NamingStrategy$SuffixingRandom$BaseNameResolver
- vb.net - 计算多行文本框中的特定字符串(VB.Net)
- regex - 如何替换换行符并保留剩余的字符串
- python - 如何知道 file.write() 何时完成