using Avalonia_Services.Services.AuthService; using System; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Authentication { /// /// PC 端全局 Token 服务,管理全局唯一的访问 Token。 /// 支持授权码登录、Token 刷新、有效性验证和登出, /// 在第三方授权暂时失败时使用缩短有效期的临时 Token。 /// public sealed class PcGlobalTokenService(IPcThirdPartyAuthorizationClient thirdPartyClient) { /// /// 超级管理员角色集合。 /// private static readonly string[] SuperRoles = ["SuperAdmin", "Admin"]; /// /// 线程同步锁。 /// private readonly object _syncRoot = new(); /// /// 当前 Token 状态。 /// private PcTokenState? _current; /// /// 正常 Token 有效期(8 小时)。 /// private static readonly TimeSpan NormalLifetime = TimeSpan.FromHours(8); /// /// 第三方暂时失败时的 Token 有效期(20 分钟)。 /// private static readonly TimeSpan TemporaryFailureLifetime = TimeSpan.FromMinutes(20); /// /// 第三方暂时失败的最长容忍窗口(24 小时),超出后清除 Token。 /// private static readonly TimeSpan MaxTemporaryFailureWindow = TimeSpan.FromHours(24); /// /// 使用授权码进行登录授权,验证成功后颁发全局 Token。 /// /// 第三方授权码。 /// 取消令牌。 /// Token 响应;若授权码无效则返回 null。 public async Task AuthorizeAsync(string? authorizationCode, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(authorizationCode)) { return null; } var result = await thirdPartyClient.ValidateAuthorizationCodeAsync(authorizationCode, cancellationToken); if (result != ThirdPartyAuthCheckResult.Valid) { return null; } return IssueToken(authorizationCode, NormalLifetime, resetTemporaryFailureWindow: true); } /// /// 刷新当前 Token,向第三方验证授权引用是否仍然有效。 /// 根据第三方返回结果决定是续期、降级为临时 Token 还是清除。 /// /// 当前 Token。 /// 取消令牌。 /// 新的 Token 响应;若授权丢失则返回 null。 public async Task RefreshAsync(string? token, CancellationToken cancellationToken = default) { PcTokenState? current; lock (_syncRoot) { current = IsCurrentToken(token) ? _current : null; } if (current is null) { return null; } var result = await thirdPartyClient.RefreshAuthorizationAsync(current.AuthorizationReference, cancellationToken); return result switch { ThirdPartyAuthCheckResult.Valid => IssueToken(current.AuthorizationReference, NormalLifetime, resetTemporaryFailureWindow: true), ThirdPartyAuthCheckResult.AuthorizationLost => ClearAndReturnNull(), ThirdPartyAuthCheckResult.TemporaryFailure => RefreshAfterTemporaryFailure(current), _ => null, }; } /// /// 验证 Token 是否有效,若已过期则尝试自动刷新。 /// /// 要验证的 Token。 /// 取消令牌。 /// Token 是否有效。 public async Task ValidateAsync(string? token, CancellationToken cancellationToken = default) { PcTokenState? current; lock (_syncRoot) { if (!IsCurrentToken(token)) { return false; } current = _current; if (current is not null && current.ExpiresAt > DateTime.UtcNow) { return true; } } return await RefreshAsync(token, cancellationToken) is not null; } /// /// 登出并清除当前 Token。 /// /// 要清除的 Token。 public void Logout(string? token) { lock (_syncRoot) { if (IsCurrentToken(token)) { _current = null; } } } /// /// 颁发新的全局 Token。 /// /// 授权引用标识。 /// Token 有效期。 /// 是否重置暂时失败窗口。 /// 包含 Token 和过期时间的响应。 private PcTokenResponse IssueToken(string authorizationReference, TimeSpan lifetime, bool resetTemporaryFailureWindow) { var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)); var now = DateTime.UtcNow; var state = new PcTokenState( HashToken(token), authorizationReference, now.Add(lifetime), resetTemporaryFailureWindow ? null : _current?.TemporaryFailureStartedAt ?? now); lock (_syncRoot) { _current = state; } return new PcTokenResponse(token, state.ExpiresAt, SuperRoles); } /// /// 在第三方暂时失败时刷新 Token。若超出最大容忍窗口则清除 Token。 /// /// 当前 Token 状态。 /// 新的临时 Token 响应;若超出容忍窗口则返回 null。 private PcTokenResponse? RefreshAfterTemporaryFailure(PcTokenState current) { var startedAt = current.TemporaryFailureStartedAt ?? DateTime.UtcNow; if (DateTime.UtcNow - startedAt > MaxTemporaryFailureWindow) { return ClearAndReturnNull(); } return IssueToken(current.AuthorizationReference, TemporaryFailureLifetime, resetTemporaryFailureWindow: false); } /// /// 清除当前 Token 并返回 null。 /// /// 始终返回 null。 private PcTokenResponse? ClearAndReturnNull() { lock (_syncRoot) { _current = null; } return null; } /// /// 检查给定 Token 是否与当前持有的 Token 匹配。 /// /// 要检查的 Token。 /// 是否匹配。 private bool IsCurrentToken(string? token) { return !string.IsNullOrWhiteSpace(token) && _current is not null && string.Equals(_current.TokenHash, HashToken(token), StringComparison.Ordinal); } /// /// 对 Token 原文进行 SHA256 哈希,返回十六进制字符串。 /// /// Token 原文。 /// SHA256 哈希后的十六进制字符串。 private static string HashToken(string token) { var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(token)); return Convert.ToHexString(bytes); } /// /// 保存当前全局 Token 的内部状态。 /// /// Token 的 SHA256 哈希值。 /// 授权引用标识。 /// 过期时间。 /// 第三方暂时失败的起始时间。 private sealed record PcTokenState( string TokenHash, string AuthorizationReference, DateTime ExpiresAt, DateTime? TemporaryFailureStartedAt); } }