AvaloniaStack/Avalonia-PC/Authentication/PcGlobalTokenService.cs
luoqian fc6f9f6bc3 docs: 补全全部缺失的 XML 文档注释(中文)
- 为全部 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 项目编译通过
2026-05-18 11:35:13 +08:00

228 lines
8.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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