This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
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('/', '_');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user