为什么 Stripe-Signature header 永远不会匹配 request.body 的签名?
Why does the Stripe-Signature header never match the signature of request.body?
我将 Python 与 Django Rest 框架一起使用,并尝试从 stripe 正确接收 webhook 事件。
但是我经常收到这个错误:
stripe.error.SignatureVerificationError: No signatures found matching the expected signature for payload
这是代码:
WEBHOOK_SECRET = settings.STRIPE_WEBHOOK_SK
@csrf_exempt
def webhook(request):
sig_header = request.headers.get('Stripe-Signature', None)
payload = request.body
try:
event = stripe.Webhook.construct_event(
payload=payload,
sig_header=sig_header,
secret=WEBHOOK_SECRET
)
except ValueError as e:
raise e
except stripe.error.SignatureVerificationError as e:
raise e
return HttpResponse(status=200)
我也试过像这样修改请求body格式:
payload = request.body.decode('utf-8')
# and also
payload = json.loads(request.body)
但运气不好。
错误来自 verify_header()
class WebhookSignature
class 中的方法。
这是方法失败的部分:
if not any(util.secure_compare(expected_sig, s) for s in signatures):
raise error.SignatureVerificationError(
"No signatures found matching the expected signature for payload",
header,
payload,
)
所以我在这行之前打印了exptected_sig
和signatures
,发现无论request.body
是什么格式,signatures
总是存在的(这很好), 但它们从不匹配来自 header.
的签名
这是为什么?
当 Stripe 计算它发送给您的事件的签名时,它使用代表整个事件内容的特定“有效负载”。签名是在那个确切的有效负载上完成的,对其进行任何更改,例如添加新行、删除 space 或更改属性的顺序,都会更改有效负载和相应的签名。
当您验证签名时,您需要确保传递的是 Stripe 发送给您的准确原始负载,否则您计算出的签名将与 Stripe 签名不匹配。
Frameworks 有时会尝试在收到请求时提供帮助,它们会检测 JSON 并自动为您解析。这意味着您认为您获得的是“原始 payload/body”,但实际上您获得的是替代版本。它具有相同的内容,但与 Stripe 发送给您的内容不匹配。
例如,这在 Node.js 中的 Express 中很常见。因此,作为开发人员,您必须明确请求 Stripe 发送给您的确切 raw/original 有效负载。以及如何做到这一点可能因多种因素而异。 stripe-node github 上有 2 个问题,有许多潜在的修复 here and here。
对于 Django,可能会发生同样的情况,您需要确保您的代码请求原始负载。您似乎按预期使用 request.body
,但这是您想要进一步研究的一件事。
此外,另一个常见错误是使用了错误的 Webhook 密码。例如,如果您使用 Stripe CLI,它会为您创建一个 new 机密,这与您在仪表板中看到的此 Webhook 端点的机密不同。您需要确保根据您所在的环境使用正确的密钥。
我将 Python 与 Django Rest 框架一起使用,并尝试从 stripe 正确接收 webhook 事件。
但是我经常收到这个错误:
stripe.error.SignatureVerificationError: No signatures found matching the expected signature for payload
这是代码:
WEBHOOK_SECRET = settings.STRIPE_WEBHOOK_SK
@csrf_exempt
def webhook(request):
sig_header = request.headers.get('Stripe-Signature', None)
payload = request.body
try:
event = stripe.Webhook.construct_event(
payload=payload,
sig_header=sig_header,
secret=WEBHOOK_SECRET
)
except ValueError as e:
raise e
except stripe.error.SignatureVerificationError as e:
raise e
return HttpResponse(status=200)
我也试过像这样修改请求body格式:
payload = request.body.decode('utf-8')
# and also
payload = json.loads(request.body)
但运气不好。
错误来自 verify_header()
class WebhookSignature
class 中的方法。
这是方法失败的部分:
if not any(util.secure_compare(expected_sig, s) for s in signatures):
raise error.SignatureVerificationError(
"No signatures found matching the expected signature for payload",
header,
payload,
)
所以我在这行之前打印了exptected_sig
和signatures
,发现无论request.body
是什么格式,signatures
总是存在的(这很好), 但它们从不匹配来自 header.
这是为什么?
当 Stripe 计算它发送给您的事件的签名时,它使用代表整个事件内容的特定“有效负载”。签名是在那个确切的有效负载上完成的,对其进行任何更改,例如添加新行、删除 space 或更改属性的顺序,都会更改有效负载和相应的签名。
当您验证签名时,您需要确保传递的是 Stripe 发送给您的准确原始负载,否则您计算出的签名将与 Stripe 签名不匹配。
Frameworks 有时会尝试在收到请求时提供帮助,它们会检测 JSON 并自动为您解析。这意味着您认为您获得的是“原始 payload/body”,但实际上您获得的是替代版本。它具有相同的内容,但与 Stripe 发送给您的内容不匹配。
例如,这在 Node.js 中的 Express 中很常见。因此,作为开发人员,您必须明确请求 Stripe 发送给您的确切 raw/original 有效负载。以及如何做到这一点可能因多种因素而异。 stripe-node github 上有 2 个问题,有许多潜在的修复 here and here。
对于 Django,可能会发生同样的情况,您需要确保您的代码请求原始负载。您似乎按预期使用 request.body
,但这是您想要进一步研究的一件事。
此外,另一个常见错误是使用了错误的 Webhook 密码。例如,如果您使用 Stripe CLI,它会为您创建一个 new 机密,这与您在仪表板中看到的此 Webhook 端点的机密不同。您需要确保根据您所在的环境使用正确的密钥。