131 lines
3.8 KiB
C#
131 lines
3.8 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using AmtScanner.Api.Configuration;
|
|
using AmtScanner.Api.Models;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
namespace AmtScanner.Api.Services;
|
|
|
|
/// <summary>
|
|
/// JWT 服务实现
|
|
/// </summary>
|
|
public class JwtService : IJwtService
|
|
{
|
|
private readonly JwtSettings _settings;
|
|
private readonly SymmetricSecurityKey _securityKey;
|
|
|
|
public JwtService(IOptions<JwtSettings> settings)
|
|
{
|
|
_settings = settings.Value;
|
|
_securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.SecretKey));
|
|
}
|
|
|
|
public string GenerateAccessToken(User user, IEnumerable<string> roles)
|
|
{
|
|
var claims = new List<Claim>
|
|
{
|
|
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
new(ClaimTypes.Name, user.UserName),
|
|
new("userId", user.Id.ToString()),
|
|
new("userName", user.UserName)
|
|
};
|
|
|
|
// 添加角色 claims
|
|
foreach (var role in roles)
|
|
{
|
|
claims.Add(new Claim(ClaimTypes.Role, role));
|
|
claims.Add(new Claim("roles", role));
|
|
}
|
|
|
|
// 添加邮箱(如果有)
|
|
if (!string.IsNullOrEmpty(user.Email))
|
|
{
|
|
claims.Add(new Claim(ClaimTypes.Email, user.Email));
|
|
}
|
|
|
|
var credentials = new SigningCredentials(_securityKey, SecurityAlgorithms.HmacSha256);
|
|
var expires = DateTime.UtcNow.AddMinutes(_settings.AccessTokenExpirationMinutes);
|
|
|
|
var token = new JwtSecurityToken(
|
|
issuer: _settings.Issuer,
|
|
audience: _settings.Audience,
|
|
claims: claims,
|
|
expires: expires,
|
|
signingCredentials: credentials
|
|
);
|
|
|
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
}
|
|
|
|
public string GenerateRefreshToken()
|
|
{
|
|
var randomBytes = new byte[64];
|
|
using var rng = RandomNumberGenerator.Create();
|
|
rng.GetBytes(randomBytes);
|
|
return Convert.ToBase64String(randomBytes);
|
|
}
|
|
|
|
public ClaimsPrincipal? ValidateToken(string token)
|
|
{
|
|
var tokenHandler = new JwtSecurityTokenHandler();
|
|
|
|
try
|
|
{
|
|
var principal = tokenHandler.ValidateToken(token, GetValidationParameters(), out _);
|
|
return principal;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public ClaimsPrincipal? GetPrincipalFromExpiredToken(string token)
|
|
{
|
|
var tokenHandler = new JwtSecurityTokenHandler();
|
|
|
|
try
|
|
{
|
|
var validationParameters = GetValidationParameters();
|
|
validationParameters.ValidateLifetime = false; // 不验证过期时间
|
|
|
|
var principal = tokenHandler.ValidateToken(token, validationParameters, out var securityToken);
|
|
|
|
if (securityToken is not JwtSecurityToken jwtToken ||
|
|
!jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return principal;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public DateTime GetRefreshTokenExpiryTime()
|
|
{
|
|
return DateTime.UtcNow.AddDays(_settings.RefreshTokenExpirationDays);
|
|
}
|
|
|
|
private TokenValidationParameters GetValidationParameters()
|
|
{
|
|
return new TokenValidationParameters
|
|
{
|
|
ValidateIssuer = true,
|
|
ValidateAudience = true,
|
|
ValidateLifetime = true,
|
|
ValidateIssuerSigningKey = true,
|
|
ValidIssuer = _settings.Issuer,
|
|
ValidAudience = _settings.Audience,
|
|
IssuerSigningKey = _securityKey,
|
|
ClockSkew = TimeSpan.Zero // 不允许时间偏差
|
|
};
|
|
}
|
|
}
|