using System.Collections.Concurrent; using System.Security.Cryptography; using ShrinkNetwork; namespace ShrinkNetwork.ServerHost.Framework; public sealed class ServerAuthStore { public sealed class AuthState { public string AuthName { get; set; } = string.Empty; public bool IsAuthenticated { get; set; } public DateTimeOffset AuthenticatedAtUtc { get; set; } public DateTimeOffset LastSeenAtUtc { get; set; } public string SessionToken { get; set; } = string.Empty; public DateTimeOffset SessionTokenExpiresAtUtc { get; set; } } private readonly ConcurrentDictionary<(ShrinkNetworkService service, long sessionId), AuthState> _states = new(); public AuthState MarkAuthenticated(ShrinkNetworkService service, long sessionId, string authName, TimeSpan tokenTtl) { var now = DateTimeOffset.UtcNow; var state = new AuthState { AuthName = authName, IsAuthenticated = true, AuthenticatedAtUtc = now, LastSeenAtUtc = now, SessionToken = CreateSessionToken(), SessionTokenExpiresAtUtc = now.Add(tokenTtl) }; _states[(service, sessionId)] = state; return CloneState(state); } public void Remove(ShrinkNetworkService service, long sessionId) { _states.TryRemove((service, sessionId), out _); } public bool IsAuthenticated(ShrinkNetworkService service, long sessionId) { return _states.TryGetValue((service, sessionId), out var state) && state.IsAuthenticated; } public bool TryGetState(ShrinkNetworkService service, long sessionId, out AuthState state) { if (_states.TryGetValue((service, sessionId), out var current)) { state = CloneState(current); return true; } state = null!; return false; } public bool TryValidateSessionToken(ShrinkNetworkService service, long sessionId, string? sessionToken, out AuthState state, out string failureReason) { if (!_states.TryGetValue((service, sessionId), out var current) || !current.IsAuthenticated) { state = null!; failureReason = "会话未认证。"; return false; } if (string.IsNullOrWhiteSpace(sessionToken)) { state = null!; failureReason = "缺少会话令牌。"; return false; } if (!string.Equals(current.SessionToken, sessionToken.Trim(), StringComparison.Ordinal)) { state = null!; failureReason = "会话令牌不匹配。"; return false; } var now = DateTimeOffset.UtcNow; if (current.SessionTokenExpiresAtUtc <= now) { state = null!; failureReason = "会话令牌已过期。"; return false; } current.LastSeenAtUtc = now; state = CloneState(current); failureReason = string.Empty; return true; } public bool TryRefreshSessionToken(ShrinkNetworkService service, long sessionId, string? sessionToken, TimeSpan tokenTtl, TimeSpan refreshWindow, out AuthState state, out string failureReason) { if (!TryValidateSessionToken(service, sessionId, sessionToken, out _, out failureReason)) { state = null!; return false; } var key = (service, sessionId); if (!_states.TryGetValue(key, out var current)) { state = null!; failureReason = "认证状态不存在。"; return false; } var now = DateTimeOffset.UtcNow; if (refreshWindow > TimeSpan.Zero && current.SessionTokenExpiresAtUtc - now > refreshWindow) { state = null!; failureReason = "当前还不在会话令牌续期窗口内。"; return false; } current.SessionToken = CreateSessionToken(); current.SessionTokenExpiresAtUtc = now.Add(tokenTtl); current.LastSeenAtUtc = now; state = CloneState(current); failureReason = string.Empty; return true; } private static AuthState CloneState(AuthState state) { return new AuthState { AuthName = state.AuthName, IsAuthenticated = state.IsAuthenticated, AuthenticatedAtUtc = state.AuthenticatedAtUtc, LastSeenAtUtc = state.LastSeenAtUtc, SessionToken = state.SessionToken, SessionTokenExpiresAtUtc = state.SessionTokenExpiresAtUtc }; } private static string CreateSessionToken() { var buffer = new byte[32]; RandomNumberGenerator.Fill(buffer); return Convert.ToBase64String(buffer) .TrimEnd('=') .Replace('+', '-') .Replace('/', '_'); } }