8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

动态刷新和访问令牌更新。ASP.NET Core 8

Riccardo Benedetti 1月前

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 重新发送。

帖子版权声明 1、本帖标题:动态刷新和访问令牌更新。ASP.NET Core 8
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Riccardo Benedetti在本站《authentication》版块原创发布, 转载请注明出处!
最新回复 (0)
返回
作者最近主题: