using HuanMeng.DotNetCore.JwtInfrastructure.Interface; using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace HuanMeng.DotNetCore.JwtInfrastructure { /// /// jwt帮助类 /// /// public class JwtAuthManager(JwtTokenConfig jwtTokenConfig) : IJwtAuthManager { /// /// 保存刷新token /// public IImmutableDictionary UsersRefreshTokensReadOnlyDictionary => _usersRefreshTokens.ToImmutableDictionary(); /// /// 后面可以存储在数据库或分布式缓存中 /// private readonly ConcurrentDictionary _usersRefreshTokens = new(); /// /// 获取加密字段 /// private readonly byte[] _secret = Encoding.UTF8.GetBytes(jwtTokenConfig.Secret); /// /// 删除过期token /// /// public void RemoveExpiredRefreshTokens(DateTime now) { var expiredTokens = _usersRefreshTokens.Where(x => x.Value.ExpireAt < now).ToList(); foreach (var expiredToken in expiredTokens) { _usersRefreshTokens.TryRemove(expiredToken.Key, out _); } } /// /// 根据用户名删除token /// /// public void RemoveRefreshTokenByUserName(string userName) { var refreshTokens = _usersRefreshTokens.Where(x => x.Value.UserName == userName).ToList(); foreach (var refreshToken in refreshTokens) { _usersRefreshTokens.TryRemove(refreshToken.Key, out _); } } /// /// 创建token /// /// 用户名 /// 用户项 /// 过期时间 /// public JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now) { var shouldAddAudienceClaim = string.IsNullOrWhiteSpace(claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Aud)?.Value); //创建token var jwtToken = new JwtSecurityToken( jwtTokenConfig.Issuer, shouldAddAudienceClaim ? jwtTokenConfig.Audience : string.Empty, claims, expires: now.AddMinutes(jwtTokenConfig.AccessTokenExpiration), signingCredentials: new SigningCredentials(new SymmetricSecurityKey(_secret), SecurityAlgorithms.HmacSha256Signature)); var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtToken); //创建刷新token var refreshToken = new JwtRefreshToken { UserName = username, TokenString = GenerateRefreshTokenString(), ExpireAt = now.AddMinutes(jwtTokenConfig.RefreshTokenExpiration) }; _usersRefreshTokens.AddOrUpdate(refreshToken.TokenString, refreshToken, (_, _) => refreshToken); return new JwtAuthResult { AccessToken = accessToken, RefreshToken = refreshToken }; } /// /// 刷新token /// /// /// /// /// /// public JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now) { var (principal, jwtToken) = DecodeJwtToken(accessToken); if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature)) { throw new SecurityTokenException("无效的token"); } var userName = principal.Identity?.Name; if (!_usersRefreshTokens.TryGetValue(refreshToken, out var existingRefreshToken)) { throw new SecurityTokenException("token已失效"); } if (existingRefreshToken.UserName != userName || existingRefreshToken.ExpireAt < now) { throw new SecurityTokenException("token不匹配"); } //创建新的token return GenerateTokens(userName, principal.Claims.ToArray(), now); } /// /// 解析token /// /// /// /// public (ClaimsPrincipal, JwtSecurityToken?) DecodeJwtToken(string token) { if (string.IsNullOrWhiteSpace(token)) { throw new SecurityTokenException("token不能为空"); } var principal = new JwtSecurityTokenHandler() .ValidateToken(token, new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = jwtTokenConfig.Issuer, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(_secret), ValidAudience = jwtTokenConfig.Audience, ValidateAudience = true, ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(5) }, out var validatedToken); return (principal, validatedToken as JwtSecurityToken); } /// /// 获取刷新的token /// /// private static string GenerateRefreshTokenString() { var randomNumber = new byte[32]; using var randomNumberGenerator = RandomNumberGenerator.Create(); randomNumberGenerator.GetBytes(randomNumber); return Convert.ToBase64String(randomNumber); } } }