ChouBox/Utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtAuthManager.cs
2025-04-23 19:20:23 +08:00

169 lines
6.5 KiB
C#

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
{
/// <summary>
/// jwt帮助类
/// </summary>
/// <param name="jwtTokenConfig"></param>
public class JwtAuthManager(JwtTokenConfig jwtTokenConfig) : IJwtAuthManager
{
/// <summary>
/// 保存刷新token
/// </summary>
public IImmutableDictionary<string, JwtRefreshToken> UsersRefreshTokensReadOnlyDictionary => _usersRefreshTokens.ToImmutableDictionary();
/// <summary>
/// 后面可以存储在数据库或分布式缓存中
/// </summary>
private readonly ConcurrentDictionary<string, JwtRefreshToken> _usersRefreshTokens = new();
/// <summary>
/// 获取加密字段
/// </summary>
private readonly byte[] _secret = Encoding.UTF8.GetBytes(jwtTokenConfig.Secret);
/// <summary>
/// 删除过期token
/// </summary>
/// <param name="now"></param>
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 _);
}
}
/// <summary>
/// 根据用户名删除token
/// </summary>
/// <param name="userName"></param>
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 _);
}
}
/// <summary>
/// 创建token
/// </summary>
/// <param name="username">用户名</param>
/// <param name="claims">用户项</param>
/// <param name="now">过期时间</param>
/// <returns></returns>
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
};
}
/// <summary>
/// 刷新token
/// </summary>
/// <param name="refreshToken"></param>
/// <param name="accessToken"></param>
/// <param name="now"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
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);
}
/// <summary>
/// 解析token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
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);
}
/// <summary>
/// 获取刷新的token
/// </summary>
/// <returns></returns>
private static string GenerateRefreshTokenString()
{
var randomNumber = new byte[32];
using var randomNumberGenerator = RandomNumberGenerator.Create();
randomNumberGenerator.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
}