165 lines
5.1 KiB
C#
165 lines
5.1 KiB
C#
using Entities.Configuration;
|
||
using Microsoft.AspNetCore.Identity;
|
||
using Microsoft.Extensions.Options;
|
||
using Microsoft.IdentityModel.Tokens;
|
||
using System.IdentityModel.Tokens.Jwt;
|
||
using System.Security.Claims;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using Entities.DTO;
|
||
|
||
namespace TechHelper.Services.Beta
|
||
{
|
||
/// <summary>
|
||
/// 用户认证服务,实现 JWT 令牌生成、刷新令牌生成、过期令牌解析等功能。
|
||
/// </summary>
|
||
public class AuthenticationService : IAuthenticationService
|
||
{
|
||
private readonly JwtConfiguration _jwtSettings;
|
||
private readonly JwtSecurityTokenHandler _jwtHandler;
|
||
private readonly UserManager<Entities.Contracts.User> _userManager;
|
||
private readonly IClassService _classService;
|
||
|
||
/// <summary>
|
||
/// 构造函数,注入 JWT 配置、用户管理器和班级服务。
|
||
/// </summary>
|
||
public AuthenticationService(IOptions<JwtConfiguration> jwtSettings, UserManager<Entities.Contracts.User> userManager, IClassService classService)
|
||
{
|
||
_jwtSettings = jwtSettings.Value;
|
||
_jwtHandler = new JwtSecurityTokenHandler();
|
||
_userManager = userManager;
|
||
_classService = classService;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成指定用户的 JWT 访问令牌。
|
||
/// </summary>
|
||
/// <param name="user">用户实体</param>
|
||
/// <returns>JWT 字符串</returns>
|
||
public async Task<string> GetToken(Entities.Contracts.User user)
|
||
{
|
||
var signingCredentials = GetSigningCredentials();
|
||
var claims = await GetClaims(user);
|
||
var tokenOptions = GenerateTokenOptions(signingCredentials, claims);
|
||
return _jwtHandler.WriteToken(tokenOptions);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 JWT 签名凭证。
|
||
/// </summary>
|
||
/// <returns>签名凭证</returns>
|
||
private SigningCredentials GetSigningCredentials()
|
||
{
|
||
var key = Encoding.UTF8.GetBytes(_jwtSettings.SecurityKey);
|
||
var secret = new SymmetricSecurityKey(key);
|
||
return new SigningCredentials(secret, SecurityAlgorithms.HmacSha256);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取用户的声明信息,包括角色和班级信息。
|
||
/// </summary>
|
||
/// <param name="user">用户实体</param>
|
||
/// <returns>声明集合</returns>
|
||
private async Task<IEnumerable<Claim>> GetClaims(Entities.Contracts.User user)
|
||
{
|
||
var claims = new List<Claim>
|
||
{
|
||
new Claim(ClaimTypes.Name, user.Email)
|
||
};
|
||
|
||
// 添加用户角色声明
|
||
var roles = await _userManager.GetRolesAsync(user);
|
||
foreach (var role in roles)
|
||
{
|
||
claims.Add(new Claim(ClaimTypes.Role, role));
|
||
}
|
||
|
||
#region ClassInfo
|
||
// 添加用户班级信息声明
|
||
var classInfo = await _classService.GetUserInjoinedClasses(user.Id);
|
||
if (classInfo.Status)
|
||
{
|
||
var classs = classInfo.Result as UserClassDetailInfoDto;
|
||
if (classs == null) return claims;
|
||
foreach (var c in classs.UserClassInfos)
|
||
{
|
||
claims.Add(new Claim("Grade", c.Grade.ToString()));
|
||
claims.Add(new Claim("Class", c.Class.ToString()));
|
||
}
|
||
}
|
||
#endregion
|
||
return claims;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成 JWT 令牌对象。
|
||
/// </summary>
|
||
/// <param name="signingCredentials">签名凭证</param>
|
||
/// <param name="claims">声明集合</param>
|
||
/// <returns>JWT 令牌对象</returns>
|
||
private JwtSecurityToken GenerateTokenOptions(SigningCredentials signingCredentials, IEnumerable<Claim> claims)
|
||
{
|
||
var tokenOptions = new JwtSecurityToken(
|
||
issuer: _jwtSettings.ValidIssuer,
|
||
audience: _jwtSettings.ValidAudience,
|
||
claims: claims,
|
||
expires: DateTime.UtcNow.AddMinutes(Convert.ToDouble(
|
||
_jwtSettings.ExpiryInMinutes)),
|
||
signingCredentials: signingCredentials);
|
||
|
||
return tokenOptions;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成安全的刷新令牌(Base64 字符串)。
|
||
/// </summary>
|
||
/// <returns>刷新令牌</returns>
|
||
public string GenerateRefreshToken()
|
||
{
|
||
var randomNumber = new byte[32];
|
||
using (var rng = RandomNumberGenerator.Create())
|
||
{
|
||
rng.GetBytes(randomNumber);
|
||
return Convert.ToBase64String(randomNumber);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从过期的 JWT 令牌中获取声明主体(不验证过期时间)。
|
||
/// </summary>
|
||
/// <param name="token">过期的 JWT 令牌</param>
|
||
/// <returns>声明主体</returns>
|
||
/// <exception cref="SecurityTokenException">令牌无效时抛出</exception>
|
||
public ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
|
||
{
|
||
var tokenValidationParameters = new TokenValidationParameters
|
||
{
|
||
ValidateAudience = true,
|
||
ValidateIssuer = true,
|
||
ValidateIssuerSigningKey = true,
|
||
IssuerSigningKey = new SymmetricSecurityKey(
|
||
Encoding.UTF8.GetBytes(_jwtSettings.SecurityKey)),
|
||
ValidateLifetime = false, // 不验证过期时间
|
||
ValidIssuer = _jwtSettings.ValidIssuer,
|
||
ValidAudience = _jwtSettings.ValidAudience,
|
||
};
|
||
|
||
var tokenHandler = new JwtSecurityTokenHandler();
|
||
SecurityToken securityToken;
|
||
|
||
var principal = tokenHandler.ValidateToken(token,
|
||
tokenValidationParameters, out securityToken);
|
||
|
||
var jwtSecurityToken = securityToken as JwtSecurityToken;
|
||
if (jwtSecurityToken == null ||
|
||
!jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256,
|
||
StringComparison.InvariantCultureIgnoreCase))
|
||
{
|
||
throw new SecurityTokenException("Invalid token");
|
||
}
|
||
|
||
return principal;
|
||
}
|
||
}
|
||
}
|