C# .NET 6中Fireblocks API客户端报'invalid exp value'求修复
解决Fireblocks API "invalid exp value" 错误的C# .NET 6实现修正
问题根源是JWT标准要求iat(签发时间)和exp(过期时间)必须是Unix时间戳(从1970-01-01 UTC到目标时间的秒数),你的代码直接传入了DateTime对象,导致JWT中的exp值格式不符合Fireblocks API的校验要求。以下是修正后的完整代码:
using System.Security.Cryptography; using System.Text; using Newtonsoft.Json.Linq; public class FireblocksHttpClient { private readonly string baseUrl = "https://api.fireblocks.io"; private readonly string apiKey; private readonly RSA privateKey; private readonly HttpClient client; public FireblocksHttpClient(string apiKey, string apiSecret) { this.apiKey = apiKey; client = new HttpClient(); // 清理私钥格式并导入 byte[] keyBytes = Convert.FromBase64String(apiSecret .Replace("-----BEGIN PRIVATE KEY-----", "") .Replace("-----END PRIVATE KEY-----", "") .Replace("\n", "")); var rsa = RSA.Create(); rsa.ImportPkcs8PrivateKey(keyBytes, out _); privateKey = rsa; } public async Task<JObject> Get(string path) { var req = new HttpRequestMessage(HttpMethod.Get, $"{baseUrl}{path}"); req.Headers.Add("X-API-Key", apiKey); req.Headers.Add("Authorization", $"Bearer {SignJwt(path)}"); using var resp = await client.SendAsync(req); resp.EnsureSuccessStatusCode(); // 可选:主动抛出HTTP错误便于调试 var body = await resp.Content.ReadAsStringAsync(); return JObject.Parse(body); } public async Task<JObject> Post(string path, JObject data) { var req = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}{path}"); req.Headers.Add("X-API-Key", apiKey); var dataStr = data.ToString(); req.Headers.Add("Authorization", $"Bearer {SignJwt(path, dataStr)}"); req.Content = new StringContent(dataStr, Encoding.UTF8, "application/json"); using var resp = await client.SendAsync(req); resp.EnsureSuccessStatusCode(); // 可选:主动抛出HTTP错误便于调试 var body = await resp.Content.ReadAsStringAsync(); return JObject.Parse(body); } private string SignJwt(string path) => SignJwt(path, string.Empty); private string SignJwt(string path, string dataJSONString) { using var sha256 = SHA256.Create(); byte[] dataHash = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataJSONString)); string bodyHash = BitConverter.ToString(dataHash).Replace("-", "").ToLower(); var now = DateTime.UtcNow; // 转换为Unix时间戳(秒),符合JWT标准要求 var unixNow = ((DateTimeOffset)now).ToUnixTimeSeconds(); var expiresUnix = unixNow + 55; // 55秒后过期 var jwtClaims = new { sub = apiKey, iat = unixNow, exp = expiresUnix, nonce = Guid.NewGuid().ToString(), uri = path, bodyHash }; var jwtHeader = new Dictionary<string, object> { { "alg", "RS256" }, { "typ", "JWT" } }; // 直接使用已初始化的privateKey,无需重复导出导入RSA参数 var jwt = Jose.JWT.Encode(jwtClaims, privateKey, Jose.JwsAlgorithm.RS256, extraHeaders: jwtHeader); return jwt; } }
关键修改点:
- Unix时间戳转换:将
iat和exp从DateTime对象改为Unix时间戳(秒数),使用DateTimeOffset.ToUnixTimeSeconds()实现,完全符合JWT标准格式。 - 简化RSA密钥操作:移除了不必要的RSA参数导出和重新导入步骤,直接使用构造函数中初始化的
privateKey实例,减少冗余代码并提升性能。 - 调试增强:添加
resp.EnsureSuccessStatusCode()主动抛出HTTP错误,方便快速定位请求异常。
内容的提问来源于stack exchange,提问作者Keisha W
相关产品推荐
相关产品推荐

