using LiveForum.Code.ExceptionExtend; using LiveForum.Code.JwtInfrastructure.Interface; using LiveForum.Code.Redis.Contract; using LiveForum.Code.Utility; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace LiveForum.Code.JwtInfrastructure { /// /// 获取Redis存储的Jwt帮助类 /// public class JwtRedisAuthManager : IJwtAuthManager { private readonly IRedisService _redisService; private readonly IOptionsMonitor _jwtTokenConfigMonitor; private readonly ILogger _logger; private const string JWT_USER_PREFIX = "jwt:user:"; public JwtRedisAuthManager(IRedisService redisService, IOptionsMonitor jwtTokenConfigMonitor, ILogger logger) { _redisService = redisService; _jwtTokenConfigMonitor = jwtTokenConfigMonitor; _logger = logger; } /// /// 获取当前 JWT 配置(每次调用都获取最新配置) /// private JwtTokenConfig GetCurrentConfig() => _jwtTokenConfigMonitor.CurrentValue; public async Task GenerateTokensAsync(string username, List claims, DateTime now) { try { // 获取最新配置 var config = GetCurrentConfig(); var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(config.Secret); var expireAt = now.AddHours(config.AccessTokenExpiration); //claims[] if (claims == null) { claims = new List(); } claims.Add(new Claim("userName", username)); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = expireAt, Issuer = config.Issuer, Audience = config.Audience, SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); var tokenString = tokenHandler.WriteToken(token); var jwtAccessToken = new JwtAccessToken { UserName = username, Token = tokenString, ExpireAt = expireAt }; var md5Str = tokenString.ToMD5(); // 存储到Redis var userKey = $"{JWT_USER_PREFIX}:{username}"; //var expiration = expireAt - now; 过期时间不等于缓存时间 await _redisService.SetAsync(userKey, tokenString, TimeSpan.FromMinutes(60)); return jwtAccessToken; } catch (Exception ex) { _logger.LogError(ex, "生成JWT令牌失败,用户名: {Username}", username); throw; } } public Task GetAccessTokenExpireAtAsync() { try { // 获取最新配置 var config = GetCurrentConfig(); return Task.FromResult(DateTime.Now.AddMinutes(config.AccessTokenExpiration)); } catch (Exception ex) { _logger.LogError(ex, "获取访问令牌过期时间失败"); throw; } } public Task VerificationJwtAccessTokenAsync(string token) { try { if (string.IsNullOrEmpty(token)) return Task.FromResult(false); // 获取最新配置 var config = GetCurrentConfig(); // 验证JWT令牌格式和签名 var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(config.Secret); try { tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = true, ValidIssuer = config.Issuer, ValidateAudience = true, ValidAudience = config.Audience, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }, out SecurityToken validatedToken); return Task.FromResult(true); } catch { return Task.FromResult(false); } } catch (Exception ex) { _logger.LogError(ex, "验证JWT令牌失败"); return Task.FromResult(false); } } /// /// /// /// /// /// /// /// public async Task WriteJwtCacheAsync(string username, string token, DateTime expireAt) { var timespan = (expireAt - DateTime.Now); if (timespan.TotalSeconds < 0) { throw new ArgumentException("令牌已过期"); } //验证token是否在redis中存在 var userKey = $"{JWT_USER_PREFIX}{username}"; //每次缓存60分钟 await _redisService.SetAsync(userKey, token, TimeSpan.FromMinutes(60)); } public async Task DecodeJwtTokenAsync(string token) { try { var userInfo = await DecodeBaseJwtTokenAsync(token); //验证token是否在redis中存在 var userKey = $"{JWT_USER_PREFIX}{userInfo.UserName}"; var storedToken = await _redisService.GetAsync(userKey); if (storedToken != token) { throw new RedisNullException("token无效或已被注销"); } return userInfo; } catch (Exception ex) { _logger.LogError(ex, "解密JWT令牌失败"); throw; } } public async Task DecodeBaseJwtTokenAsync(string token) { if (string.IsNullOrEmpty(token)) throw new ArgumentException("Token不能为空"); var isSuccess = await VerificationJwtAccessTokenAsync(token); if (!isSuccess) { throw new ArgumentException("token已过期"); } var tokenHandler = new JwtSecurityTokenHandler(); var jwtToken = tokenHandler.ReadJwtToken(token); var username = jwtToken.Claims.FirstOrDefault(x => x.Type == "userName")?.Value ?? jwtToken.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value ?? jwtToken.Claims.FirstOrDefault(x => x.Type == "sub")?.Value ?? string.Empty; var claims = jwtToken.Claims.ToArray(); var expireAt = jwtToken.ValidTo; if (string.IsNullOrEmpty(username)) { throw new ArgumentException("无法从令牌中提取用户名"); } return new JwtUserInfoModel() { UserName = username, Claims = claims.ToList(), ExpireAt = expireAt }; } public async Task> GetJwtAccessTokenListAsync() { try { var db = await _redisService.GetDatabaseAsync(); var server = db.Multiplexer.GetServer(db.Multiplexer.GetEndPoints().First()); var keys = server.Keys(pattern: JWT_USER_PREFIX + "*"); var tokens = new List(); foreach (var key in keys) { var tokenString = await _redisService.GetAsync(key.ToString()); if (!string.IsNullOrEmpty(tokenString)) { // 从token中解析用户名和过期时间 var tokenHandler = new JwtSecurityTokenHandler(); try { var jwtToken = tokenHandler.ReadJwtToken(tokenString); var username = jwtToken.Claims.FirstOrDefault(x => x.Type == "userName")?.Value ?? jwtToken.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name)?.Value ?? jwtToken.Claims.FirstOrDefault(x => x.Type == "sub")?.Value ?? string.Empty; var jwtAccessToken = new JwtAccessToken { UserName = username, Token = tokenString, ExpireAt = jwtToken.ValidTo }; tokens.Add(jwtAccessToken); } catch (Exception ex) { _logger.LogWarning(ex, "解析JWT令牌失败,跳过该令牌: {Token}", tokenString); continue; } } } return tokens; } catch (Exception ex) { _logger.LogError(ex, "获取JWT令牌列表失败"); throw; } } public async Task GetJwtAccessTokenCountAsync() { try { var db = await _redisService.GetDatabaseAsync(); var server = db.Multiplexer.GetServer(db.Multiplexer.GetEndPoints().First()); var keys = server.Keys(pattern: JWT_USER_PREFIX + "*"); return keys.Count(); } catch (Exception ex) { _logger.LogError(ex, "获取JWT令牌数量失败"); throw; } } public async Task RemoveTokenByUserNameAsync(string userName) { try { if (string.IsNullOrEmpty(userName)) return; var userKey = $"{JWT_USER_PREFIX}{userName}"; await _redisService.RemoveAsync(userKey); _logger.LogInformation("已删除用户 {UserName} 的JWT令牌", userName); } catch (Exception ex) { _logger.LogError(ex, "按用户名删除JWT令牌失败,用户名: {Username}", userName); throw; } } public async Task RemoveAllTokensAsync() { try { var db = await _redisService.GetDatabaseAsync(); var server = db.Multiplexer.GetServer(db.Multiplexer.GetEndPoints().First()); // 删除所有用户令牌映射 var userKeys = server.Keys(pattern: JWT_USER_PREFIX + "*"); var removedCount = 0; foreach (var key in userKeys) { await _redisService.RemoveAsync(key.ToString()); removedCount++; } _logger.LogInformation("已删除所有JWT令牌,共删除 {Count} 个令牌", removedCount); } catch (Exception ex) { _logger.LogError(ex, "删除所有JWT令牌失败"); throw; } } } }