Stripe Webhook 签名验证失败 - Stripe.net

7
我正在尝试使用Jayme Davis的C#库Stripe.net实现一个stripe webhook。我已经在stripe仪表板中设置了测试端点并生成了密钥。端点被成功访问,并且将使用StripeEventUtility.ParseEvent生成StripeEvent。问题在于,使用ConstructEvent函数时,我无法使签名匹配。非常感谢任何帮助或建议。
isSignaturePresent返回false。
//call to create event
stripeEvent = ConstructEvent(json, Request.Headers["Stripe-Signature"], 
SecretFromStripeDashBoard);


private StripeEvent ConstructEvent(string json, string 
stripeSignatureHeader, string secret, int tolerance = 300)
    {
        var signatureItems = parseStripeSignature(stripeSignatureHeader);

        var signature = computeSignature(secret, signatureItems["t"].FirstOrDefault(), json);

        if (!isSignaturePresent(signature, signatureItems["v1"]))
            throw new Exception("The signature for the webhook is not present in the Stripe-Signature header.");

        //var utcNow = EpochUtcNowOverride ?? DateTime.UtcNow.ConvertDateTimeToEpoch();
        //var webhookUtc = Convert.ToInt32(signatureItems["t"].FirstOrDefault());

        //if (utcNow - webhookUtc > tolerance)
        //    throw new Exception("The webhook cannot be processed because the current timestamp is above the allowed tolerance.");

        return Mapper<StripeEvent>.MapFromJson(json);
    }

    private ILookup<string, string> parseStripeSignature(string stripeSignatureHeader)
    {
        return stripeSignatureHeader.Trim()
            .Split(',')
            .Select(item => item.Trim().Split('='))
            .ToLookup(item => item[0], item => item[1]);
    }

    private bool isSignaturePresent(string signature, IEnumerable<string> signatures)
    {
        return signatures.Any(key => secureCompare(key, signature));
    }

    private string computeSignature(string secret, string timestamp, string payload)
    {
        var secretBytes = Encoding.UTF8.GetBytes(secret);
        var payloadBytes = Encoding.UTF8.GetBytes($"{timestamp}.{payload}");

        var cryptographer = new HMACSHA256(secretBytes);
        var hash = cryptographer.ComputeHash(payloadBytes);

        return BitConverter.ToString(hash).Replace("-", "").ToLower();
    }

    private bool secureCompare(string a, string b)
    {
        if (a.Length != b.Length) return false;

        var result = 0;

        for (var i = 0; i < a.Length; i++)
        {
            result |= a[i] ^ b[i];
        }

        return result == 0;
    }
}

你是如何初始化 json 变量的? - Ywain
我使用以下代码:var json = JsonSerializer.SerializeToString(request); - stephen
1
所以你正在重新序列化已反序列化的请求体。然而,这很不可能返回与原始请求体完全相同的字符串。你需要使用由你的Web服务器/框架传递的原始请求体。 - Ywain
会尝试一下,谢谢。 - stephen
太好了,那个方法很有效,谢谢。 - stephen
这种情况在Scala中也会发生,使用Play框架时更是如此。请求正文默认解析为json格式,如果您尝试访问字符串,则是反序列化后的json,与原始正文不同,因此签名验证失败。我在这个问题中详细介绍了如何获取原始正文:https://stackoverflow.com/questions/59332440/how-to-get-the-raw-request-body-in-play/59332549#59332549 - Ali
3个回答

7

我在上面的评论中回答了这个问题,但是为了重申一下,问题出在提供给ConstructEvent方法的json字符串不包含Stripe发送的确切负载。

相反地,您使用以下方式初始化了负载:

var json = JsonSerializer.SerializeToString(request);

即你重新序列化了反序列化请求的主体。然而,你很少会得到与Stripe发送的原始有效负载相同的字符串。例如,Stripe可能发送此有效负载:

{
  "a_key": "a_value",
  "another_key": "another_value"
}

但是在反序列化和重新序列化后,您的JSON字符串可能包含以下值:

{"another_key": "another_value","a_key": "a_value"}

作为关键顺序不能保证的内容,其他格式选项(换行、缩进)也会发挥作用。
Webhook 签名是使用完全相同的负载(作为原始字符串)生成的,所以在验证签名时,您必须还提供由 Web 服务器或框架传递的确切负载。

嗨@Ywain,如何从请求体中获取精确的有效载荷。我尝试了几种方法(将主体作为[FromBody]获取,从StreamReader获取主体等),但没有解决问题。 - Isuru Siriwardana
嗨@IsuruSiriwardana。如果您使用ASP.NET,您应该能够通过以下方式获取原始正文:string rawBody; using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) { rawBody = await reader.ReadToEndAsync(); } - Ywain

1
如前面的答案所指出的,JSON字符串应与Stripe发送的负载完全相同。在我的情况下,我必须删除\r才能使它按照以下方式工作:var json = (await stripStream.ReadToEndAsync()).Replace("\r", ""); 以下是代码:
 [HttpPost("webhook")]
    public async Task<IActionResult> Index()
    {
        try
        {
            using (var stripStream = new StreamReader(HttpContext.Request.Body))
            {
                var json = (await stripStream.ReadToEndAsync()).Replace("\r", "");
                var secretKey = _settings.Value.StripeSettings.WebhookSecretKey;
                // validate webhook called by stripe only
                Logger.Information($"[WEBHOOK]: Validating webhook signature {secretKey.Substring(0, 4)}**********");
                var stripeEvent = EventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"], secretKey);
                Logger.Information($"[WEBHOOK]: Webhook signature validated");

                // handle stripe event
                await this._stripeWebhookService.HandleAsync(stripeEvent);

                return Ok();
            }
        }catch(Exception){ // handle exception}

0

我的应用程序在这个点也一直抛出异常。

 var stripeEvent = EventUtility.ConstructEvent(
              json,
              Request.Headers["Stripe-Signature"],
              secret
            );

阅读异常日志,它说我正在使用新版本的NuGet包stripe.net,而在stripe仪表板中使用旧版本。我将我的帐户更新到最新的API版本,就不再出现错误了。

如果您在其他应用程序中使用旧版本,请小心更新仪表板中的版本。如果更新破坏了您的其他应用程序,您有72小时可以撤消仪表板中的更新。对我来说,从头开始更新并不是什么大问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接