C#服务器验证Firebase JWT Token失败:RS256签名匹配问题
解决Firebase JWT RS256签名验证失败的问题
你遇到的核心问题是对RS256签名验证的逻辑理解有误——你现在尝试用公钥加密哈希值来匹配签名,但实际上JWT的RS256签名是用私钥对header.payload的哈希进行签名,验证时应该用公钥来确认这个签名是有效的,而不是反向操作。
问题根源分析
RS256的验证流程是:
- 签名生成:服务端用私钥对
header.payload的SHA256哈希执行签名操作(不是加密) - 签名验证:验证方用公钥确认签名是否与
header.payload的哈希匹配
你的代码里错误地使用了rsaCrypto.Encrypt(hashed, false),这完全搞反了逻辑——公钥加密的内容只能用私钥解密,而我们需要的是验证私钥签名的有效性,应该用VerifyData方法。
另外还要提一句:你的构造函数里有个小bug,firebaseId = FirebaseId;应该写成FirebaseId = firebaseId;,否则你的Firebase项目ID根本没被正确赋值!
修正后的关键代码
替换你验证签名的那段错误代码:
// 原错误代码 // byte[] plainText = Encoding.UTF8.GetBytes(hashChunk); // byte[] hashed = SHA256Managed.Create().ComputeHash(plainText); // byte[] encrypted = rsaCrypto.Encrypt(hashed, false); // byte[] challenge = SafeB64Decode(sections[2]); // Console.WriteLine(encrypted.SequenceEqual(challenge)); // 修正后的签名验证逻辑 byte[] headerPayloadBytes = Encoding.UTF8.GetBytes(hashChunk); byte[] signatureBytes = SafeB64Decode(sections[2]); // 使用公钥验证签名有效性 bool isValid = rsaCrypto.VerifyData( headerPayloadBytes, CryptoConfig.MapNameToOID("SHA256"), signatureBytes ); if (!isValid) { return null; // 签名验证直接失败 }
补充完整的Payload验证步骤
虽然你提到还没处理Payload,但为了符合Firebase的官方验证规范,必须添加以下检查才能确保Token完全合法:
- 检查
payload.exp(过期时间)是否晚于当前时间,拒绝过期Token - 检查
payload.aud是否等于你的Firebase项目ID - 检查
payload.iss是否为https://securetoken.google.com/<你的Firebase项目ID> - 检查
payload.sub(用户ID)是否非空
完整修正后的Verify方法示例
public async Task<string> Verify(string token) { string hashChunk = token.Substring(0, token.LastIndexOf('.')); string sanitizedToken = token.Replace('-', '+').Replace('_', '/'); string[] sections = sanitizedToken.Split('.'); // 解析并验证Header JwtHeader header = B64Json<JwtHeader>(sections[0]); if(header.alg != "RS256") { return null; } // 获取Firebase公钥 HttpResponseMessage res = await Req.GetAsync("x509/securetoken@system.gserviceaccount.com"); string keyDictStr = await res.Content.ReadAsStringAsync(); Dictionary<string, string> keyDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(keyDictStr); if(!keyDict.TryGetValue(header.kid, out string keyStr)) { return null; } // 初始化RSA加密服务 var rsaCrypto = CertFromPem(keyStr); // 验证签名 byte[] headerPayloadBytes = Encoding.UTF8.GetBytes(hashChunk); byte[] signatureBytes = SafeB64Decode(sections[2]); bool isValidSignature = rsaCrypto.VerifyData( headerPayloadBytes, CryptoConfig.MapNameToOID("SHA256"), signatureBytes ); if(!isValidSignature) { return null; } // 验证Payload合法性 JwtPayload payload = B64Json<JwtPayload>(sections[1]); var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); // 检查过期时间 if(payload.exp <= now) { return null; } // 检查发行者和受众是否匹配Firebase项目 if(payload.iss != $"https://securetoken.google.com/{FirebaseId}" || payload.aud != FirebaseId) { return null; } // 检查用户ID是否存在 if(string.IsNullOrEmpty(payload.sub)) { return null; } // 所有验证通过,返回用户ID return payload.sub; }
额外优化建议
- 建议缓存Firebase的公钥,避免每次验证都发起API请求,提升性能
- 可以考虑使用成熟的JWT库(比如
System.IdentityModel.Tokens.Jwt)来简化验证流程,减少手动处理的出错概率
内容的提问来源于stack exchange,提问作者saltyJeff
相关产品推荐
相关产品推荐

