- 为全部 5 个项目(Avalonia-API、Avalonia-Common、Avalonia-EFCore、 Avalonia-PC、Avalonia-Services)中缺失注释的类、方法、属性、字段、 接口成员等补全中文 XML 文档注释 - 共修改约 37 个文件,补全约 220+ 处注释 - 修复 ServiceEndpointCollection.cs 中 MapDelete<TService> 语法错误 - 修复 PcAuthService.cs 中 const prefix 位置错乱导致编译失败的问题 - 扫描结果:缺失项 0 - 构建结果:4/4 项目编译通过
125 lines
5.1 KiB
C#
125 lines
5.1 KiB
C#
using Avalonia_EFCore.Database;
|
||
using Avalonia_EFCore.Models;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Options;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
|
||
namespace Avalonia_API.Authentication
|
||
{
|
||
/// <summary>
|
||
/// Refresh Token 服务,负责创建、查找、撤销和轮换 Refresh Token,
|
||
/// Token 原文经 SHA256 哈希后存入数据库以保证安全性。
|
||
/// </summary>
|
||
public sealed class RefreshTokenService(AppDataContext db, IOptions<JwtOptions> options)
|
||
{
|
||
/// <summary>
|
||
/// JWT 配置选项。
|
||
/// </summary>
|
||
private readonly JwtOptions _options = options.Value;
|
||
|
||
/// <summary>
|
||
/// 创建一个新的 Refresh Token,生成随机 Token 原文并存储其哈希到数据库。
|
||
/// </summary>
|
||
/// <param name="userId">关联的用户 ID。</param>
|
||
/// <param name="device">创建设备标识(如 User-Agent)。</param>
|
||
/// <param name="ipAddress">客户端 IP 地址。</param>
|
||
/// <param name="cancellationToken">取消令牌。</param>
|
||
/// <returns>包含 Token 原文和实体记录的元组。</returns>
|
||
public async Task<(string Token, ApiRefreshTokenEntity Entity)> CreateAsync(
|
||
int userId,
|
||
string? device,
|
||
string? ipAddress,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));
|
||
var entity = new ApiRefreshTokenEntity
|
||
{
|
||
UserId = userId,
|
||
TokenHash = HashToken(token),
|
||
ExpiresAt = DateTime.UtcNow.AddDays(_options.RefreshTokenDays),
|
||
Device = device,
|
||
IpAddress = ipAddress,
|
||
};
|
||
|
||
db.ApiRefreshTokens.Add(entity);
|
||
await db.SaveChangesAsync(cancellationToken);
|
||
return (token, entity);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查找有效的 Refresh Token 实体。Token 原文会被哈希后查询数据库,
|
||
/// 仅返回未过期且未被撤销的 Token。
|
||
/// </summary>
|
||
/// <param name="token">Refresh Token 原文。</param>
|
||
/// <param name="cancellationToken">取消令牌。</param>
|
||
/// <returns>有效的 Token 实体;若无效或不存在则返回 null。</returns>
|
||
public async Task<ApiRefreshTokenEntity?> FindActiveAsync(string? token, CancellationToken cancellationToken = default)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(token))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var hash = HashToken(token);
|
||
var entity = await db.ApiRefreshTokens.FirstOrDefaultAsync(x => x.TokenHash == hash, cancellationToken);
|
||
return entity?.IsActive == true ? entity : null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 撤销指定的 Refresh Token,将其 RevokedAt 设为当前时间。
|
||
/// </summary>
|
||
/// <param name="token">要撤销的 Refresh Token 原文。</param>
|
||
/// <param name="cancellationToken">取消令牌。</param>
|
||
public async Task RevokeAsync(string? token, CancellationToken cancellationToken = default)
|
||
{
|
||
var entity = await FindActiveAsync(token, cancellationToken);
|
||
if (entity is null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
entity.RevokedAt = DateTime.UtcNow;
|
||
await db.SaveChangesAsync(cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 轮换 Refresh Token:撤销旧的并创建新的,将新 Token 的哈希关联到旧记录。
|
||
/// </summary>
|
||
/// <param name="token">旧的 Refresh Token 原文。</param>
|
||
/// <param name="device">当前设备标识。</param>
|
||
/// <param name="ipAddress">当前客户端 IP 地址。</param>
|
||
/// <param name="cancellationToken">取消令牌。</param>
|
||
/// <returns>新的 Token 对;若旧 Token 无效则返回 null。</returns>
|
||
public async Task<(string Token, ApiRefreshTokenEntity Entity)?> RotateAsync(
|
||
string? token,
|
||
string? device,
|
||
string? ipAddress,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
var current = await FindActiveAsync(token, cancellationToken);
|
||
if (current is null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var next = await CreateAsync(current.UserId, device, ipAddress, cancellationToken);
|
||
current.RevokedAt = DateTime.UtcNow;
|
||
current.ReplacedByTokenHash = next.Entity.TokenHash;
|
||
await db.SaveChangesAsync(cancellationToken);
|
||
return next;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对 Token 原文进行 SHA256 哈希,返回十六进制字符串。
|
||
/// </summary>
|
||
/// <param name="token">Token 原文。</param>
|
||
/// <returns>SHA256 哈希后的十六进制字符串。</returns>
|
||
private static string HashToken(string token)
|
||
{
|
||
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(token));
|
||
return Convert.ToHexString(bytes);
|
||
}
|
||
}
|
||
}
|