65 lines
2.2 KiB
C#
65 lines
2.2 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|