169 lines
6.5 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|