动态刷新和访问令牌更新。ASP.NET Core 8
10
0
我想使用中间件更新后端的访问和刷新令牌。当我向 API 发送请求时,我从上下文中获取令牌并将其添加到授权 header.services。
我想使用中间件在后端更新我的访问和刷新令牌。当我向 API 发送请求时,我从上下文中获取令牌并将其添加到授权标头。
services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme,
opt =>
{
opt.SaveToken = true;
opt.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["TokenSettings:Secret"]!)),
ValidateLifetime = true,
ValidIssuer = configuration["TokenSettings:Issuer"],
ValidAudience = configuration["TokenSettings:Audience"],
ClockSkew = TimeSpan.Zero,
NameClaimType = ClaimTypes.Name
};
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies[configuration["TokenSettings:CookieName"]!];
return Task.CompletedTask;
}
};
});
我想实现一个刷新令牌系统,当我收到 401 错误时,我想获取新的访问和刷新令牌并继续发送相同的请求。这是我的 RefreshTokenMiddleware:
public sealed class RefreshTokenMiddleware(RequestDelegate Next, IOptions<TokenSetting> TokenSettings)
{
private readonly RequestDelegate _next = Next;
private readonly TokenSetting _tokenSettings = TokenSettings.Value;
public async Task InvokeAsync(HttpContext context)
{
await _next(context);
if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
var refreshToken = context.Request.Cookies[_tokenSettings.RefreshCookieName];
if (refreshToken is not null)
{
var tokenService = context.RequestServices.GetRequiredService<ITokenService>();
var tokenResult = await tokenService.GenerateTokenFromRefreshToken(refreshToken);
if (tokenResult.IsSuccess)
{
context.Request.Headers["Authorization"] = $"Bearer {tokenResult.Value}";
await _next(context);
}
}
}
}
}
我的TokenService:
using Application.Services.TokenService;
namespace Infrastructure.Services.TokenService
{
public sealed class TokenService(
IHttpContextAccessor contextAccessor,
UserManager<AppUser> userManager,
IOptions<TokenSetting> tokenSettings) : ITokenService
{
private readonly HttpContext _context = contextAccessor.HttpContext!;
private readonly UserManager<AppUser> _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
private readonly TokenSetting _tokenSettings = tokenSettings?.Value ?? throw new ArgumentNullException(nameof(tokenSettings));
public async Task<Result<string>> GenerateToken(AppUser user, IList<string> roles)
{
if (user == null) return Result<string>.Failure(Error.BadRequest(UserTaskErrors.UserTaskNotFound));
if (roles == null) return Result<string>.Failure(Error.BadRequest(TokenErrors.InvalidRole));
var claims = GetClaims(user, roles);
var token = CreateJwtToken(claims);
var tokenHandler = new JwtSecurityTokenHandler();
var addClaimsResult = await _userManager.AddClaimsAsync(user, claims);
if (!addClaimsResult.Succeeded)
{
return Result<string>.Failure(Error.InvalidRequest(TokenErrors.FailedToAddClaim));
}
string accessToken = tokenHandler.WriteToken(token);
AppendCookie(accessToken);
await GenerateRefreshToken(user);
return Result<string>.Success(accessToken);
}
public async Task<Result<string>> GenerateRefreshToken(AppUser user)
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
string uniqueData = $"{user.Id}-{user.Email}-{DateTime.UtcNow.Ticks}";
byte[] uniqueBytes = Encoding.UTF8.GetBytes(uniqueData);
byte[] combinedBytes = randomNumber.Concat(uniqueBytes).ToArray();
using var sha256 = SHA256.Create();
byte[] hashBytes = sha256.ComputeHash(combinedBytes);
string refreshToken = Convert.ToBase64String(hashBytes);
await _userManager.Users.Where(u => u.Id == user.Id)
.ExecuteUpdateAsync(x => x.SetProperty(u => u.RefreshToken, refreshToken));
AppendCookie(refreshToken, true);
return Result<string>.Success(refreshToken);
}
public Result<ClaimsPrincipal> GetPrincipalFromExpiredToken(string token)
{
try
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Secret)),
ValidateLifetime = false
};
var tokenHandler = new JwtSecurityTokenHandler();
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
{
return Result<ClaimsPrincipal>.Failure(Error.InvalidRequest(TokenErrors.InvalidToken));
}
return Result<ClaimsPrincipal>.Success(principal);
}
catch (Exception)
{
return Result<ClaimsPrincipal>.Failure(Error.InvalidRequest(TokenErrors.InvalidToken));
}
}
public async Task<Result<string>> GenerateTokenFromRefreshToken(string refreshToken)
{
var user = await ValidateRefreshToken(refreshToken);
if (user == null)
{
return Result<string>.Failure(Error.InvalidRequest(TokenErrors.InvalidRefreshToken));
}
var roles = await _userManager.GetRolesAsync(user);
var result = await GenerateToken(user, roles);
if (result.IsFailure)
{
return Result<string>.Failure(result.Error);
}
return Result<string>.Success(result.Value);
}
private List<Claim> GetClaims(AppUser user, IList<string> roles)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("UserId", user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email!),
new Claim(ClaimTypes.Name, user.UserName!)
};
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
return claims;
}
private JwtSecurityToken CreateJwtToken(List<Claim> claims)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Secret));
return new JwtSecurityToken(
issuer: _tokenSettings.Issuer,
audience: _tokenSettings.Audience,
expires: DateTime.Now.AddDays(_tokenSettings.Expiration),
claims: claims,
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
}
private void AppendCookie(string token, bool isRefresh = false)
{
if (!isRefresh)
{
CookieOptions option = new()
{
HttpOnly = true,
Expires = DateTime.Now.AddSeconds(_tokenSettings.Expiration),
SameSite = SameSiteMode.Strict,
Secure = true
};
_context?.Response.Cookies.Append(_tokenSettings.CookieName, token, option);
return;
}
CookieOptions refreshTokenOption = new()
{
HttpOnly = true,
Expires = DateTime.Now.AddDays(_tokenSettings.ExpirationRefresh),
SameSite = SameSiteMode.Strict,
Secure = true
};
_context?.Response.Cookies.Append(_tokenSettings.RefreshCookieName, token, refreshTokenOption);
}
private async Task<AppUser?> ValidateRefreshToken(string refreshToken)
{
return await _userManager.Users.FirstOrDefaultAsync(u => u.RefreshToken == refreshToken);
}
}
}
我的问题是,当我的访问令牌过期并收到 401 错误时,我可以创建并存储新令牌,但仍然会再次收到 401 错误。我需要重新发送请求才能获得 200 响应。
发现问题,为什么我在更新我的令牌并存储它们后会得到 401,而且如果出现问题,为什么我会得到 200 重新发送。
动态刷新和访问令牌更新。ASP.NET Core 8
下载声明: 本站所有软件和资料均为软件作者提供或网友推荐发布而来,仅供学习和研究使用,不得用于任何商业用途。如本站不慎侵犯你的版权请联系我,我将及时处理,并撤下相关内容!
下载声明: 本站所有软件和资料均为软件作者提供或网友推荐发布而来,仅供学习和研究使用,不得用于任何商业用途。如本站不慎侵犯你的版权请联系我,我将及时处理,并撤下相关内容!
收藏的用户(0)
X
正在加载信息~