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); } } }