Initial commit: Client Doc docs Server Tools
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace XWorld.Server.Auth
|
||||
{
|
||||
// 无状态 HMAC-SHA256 token。格式: "v1.{pid}.{expUnix}.{base64url(hmac(payload))}"
|
||||
// 其中 payload = "v1.{pid}.{expUnix}"。
|
||||
public sealed class TokenService
|
||||
{
|
||||
readonly byte[] _key;
|
||||
|
||||
public TokenService(byte[] key)
|
||||
{
|
||||
if (key == null || key.Length < 16) throw new ArgumentException("key too short");
|
||||
_key = key;
|
||||
}
|
||||
|
||||
public string Issue(int pid, TimeSpan ttl)
|
||||
{
|
||||
long exp = DateTimeOffset.UtcNow.Add(ttl).ToUnixTimeSeconds();
|
||||
string payload = "v1." + pid + "." + exp;
|
||||
return payload + "." + B64Url(Sign(payload));
|
||||
}
|
||||
|
||||
public bool Verify(string token, out int pid)
|
||||
{
|
||||
pid = 0;
|
||||
if (string.IsNullOrEmpty(token)) return false;
|
||||
int lastDot = token.LastIndexOf('.');
|
||||
if (lastDot <= 0) return false;
|
||||
string payload = token.Substring(0, lastDot);
|
||||
string sig = token.Substring(lastDot + 1);
|
||||
|
||||
byte[] expected = Sign(payload);
|
||||
byte[] got;
|
||||
try { got = FromB64Url(sig); } catch { return false; }
|
||||
if (!CryptographicOperations.FixedTimeEquals(expected, got)) return false;
|
||||
|
||||
string[] parts = payload.Split('.'); // ["v1", pid, exp]
|
||||
if (parts.Length != 3 || parts[0] != "v1") return false;
|
||||
if (!int.TryParse(parts[1], out pid)) return false;
|
||||
if (!long.TryParse(parts[2], out long exp)) return false;
|
||||
if (DateTimeOffset.UtcNow.ToUnixTimeSeconds() > exp) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
byte[] Sign(string payload)
|
||||
{
|
||||
using var h = new HMACSHA256(_key);
|
||||
return h.ComputeHash(Encoding.UTF8.GetBytes(payload));
|
||||
}
|
||||
|
||||
static string B64Url(byte[] b) =>
|
||||
Convert.ToBase64String(b).TrimEnd('=').Replace('+', '-').Replace('/', '_');
|
||||
|
||||
static byte[] FromB64Url(string s)
|
||||
{
|
||||
string b = s.Replace('-', '+').Replace('_', '/');
|
||||
switch (b.Length % 4) { case 2: b += "=="; break; case 3: b += "="; break; }
|
||||
return Convert.FromBase64String(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user