在我的aspnet Core 3.1项目中,我正在使用CQRS模式和JWT身份验证,我想 在令牌过期时实现自动刷新令牌,并非每次用户都要求输入用户名和密码,我也不想将刷新令牌存储在数据库中。刷新令牌的最佳方法是什么。
我的 jwtgenerator 类用于令牌和刷新令牌。
public class JwtGenerator : IJwtGenerator
{
private readonly SymmetricSecurityKey _key;
public JwtGenerator(IConfiguration config)
{
_key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.GetSection("AppSettings:Token").Value));
}
public string GenerateToken(int size=32)
{
var randomNumber = new byte[size];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
public string CreateToken(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.UserName),
new Claim(ClaimTypes.Role, user.Role.ToString("G").ToLower())
};
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
我的登录方法如下所示:
public async Task<GetToken> Handle(Query request, CancellationToken
cancellationToken)
{
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null)
throw new UnauhtorizedException("Unauthorized");
var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password,false);
if (result.Succeeded)
{
return new GetToken()
{
Token = _jwtGenerator.CreateToken(user),
RefreshToken = _jwtGenerator.GenerateToken(32)
};
}
throw new UnauhtorizedException("Unauthorized");
}
我的配置jwt和身份验证启动:
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration.GetSection("AppSettings:Token").Value));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateAudience = false,
ValidateIssuer = false
};
});
登录后,我得到正确的结果:
{
"token": "mytoken",
"refreshToken": "tha1qH7PTx4PNoVeD7D7h/BmEZfdS42zWxriexePWlg="
}
附言我没有找到 CQRS 的任何示例。
你在那里做的事情在OAuth2
中被称为Resource Owner Password Credential
。对我来说,这看起来几乎是一个最小的实现。您需要具有刷新令牌值或至少一个引用值来锚定授权层中的令牌。它不必是数据库来存储这样的值,你可以有内存存储、物理文件或任何你想要的东西。但是无论如何,您都需要保留该值以便稍后进行验证。
下面是标准通信流,指定 OAuth2 RFC 6749 文档,其中介绍了使用刷新令牌的方案。
1.5. 刷新令牌
刷新令牌是用于获取访问令牌的凭据。 刷新 令牌由授权服务器颁发给客户端,并且 用于在当前访问令牌时获取新的访问令牌 变得无效或过期,或获取其他访问令牌 具有相同或更窄的范围(访问令牌可能具有较短的时间 生存期和少于资源授权的权限 所有者(。 颁发刷新令牌是可选的,由 授权服务器。 如果授权服务器发出刷新 令牌,它在颁发访问令牌时包含在内(即步骤 (D( 图1(。
刷新令牌是一个字符串,表示授予 资源所有者的客户端。 字符串通常不透明到 客户端。 令牌表示用于检索授权信息的标识符。 与访问令牌不同,刷新令牌是 仅用于授权服务器,永远不会发送 到资源服务器。
+--------+ +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+
Figure 2: Refreshing an Expired Access Token
图 2 所示的流程包括以下步骤:
(A( 客户端通过向 授权服务器并提供授权授权。
(B( 授权服务器对客户端进行身份验证并验证 授权授予,如果有效,则颁发访问令牌 和刷新令牌。
(C( 客户端向资源发出受保护的资源请求 服务器通过提供访问令牌。
(D( 资源服务器验证访问令牌,如果有效, 为请求提供服务。
(E( 重复步骤 (C( 和 (D(,直到访问令牌过期。 如果 客户端知道访问令牌已过期,它会跳到步骤 (G(; 否则,它会发出另一个受保护的资源请求。
(F( 由于访问令牌无效,资源服务器返回 令牌无效错误。
(G( 客户端通过身份验证请求新的访问令牌 授权服务器并显示刷新令牌。 这 客户端身份验证要求基于客户端类型 以及授权服务器策略。
(H( 授权服务器对客户端进行身份验证并验证 刷新令牌,如果有效,则颁发新的访问令牌(并且, (可选(新的刷新令牌(。
此问题是特定于实现的,您可以在应用程序级别实现最佳实践的选项太多。为此,我想说"坚持标准"。