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);
}
}