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;
}
}
}
}