You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:14:10