Django对接Stripe支付时POST外部URL的CSRF保护问题
我正在用Python的Django框架搭建网站,采用Stripe实现用户支付功能,目前处于本地开发的测试/调试阶段,距离生产构建还有很大距离。
我在CSRF保护方面遇到了瓶颈,找不到解决方案。关闭CSRF保护时,结账功能可以正常工作。
网上有一些类似问题的讨论,但由于我对CSRF保护本身了解不足,希望有人能针对我的具体场景给出指导。
这种场景应该有通用的解决方案,但我查阅了Django和Stripe的大部分文档都没有找到答案。
第一次在StackOverflow发帖,欢迎批评指正。
我写了一个视图函数,通过将用户引导至Stripe的外部结账页面来实现网站上的商品购买功能。
views.py(旧版本)
@csrf_exempt # 测试时关闭CSRF保护 def create_checkout_session(request): if request.method == 'POST': try: checkout_session = stripe.checkout.Session.create( ... ) except Exception as e: ... return redirect(checkout_session.url)
测试期间,我使用Django的@csrf_exempt装饰器关闭了视图函数的CSRF保护,以此绕过“403 CSRF验证失败”错误。
最初出现该错误是因为看到Django文档里的这段说明:
对于指向外部URL的POST表单,不应使用
{% csrf_token %},因为这会导致CSRF令牌泄露,从而引发安全漏洞。
移除@csrf_exempt装饰器后我陷入了困境,因为我的提交表单正是这种情况:如下方products.html所示,结账URL由Stripe生成(如views.py中代码),因此我认为它属于外部URL:
products.html
<form rel="noreferrer" action="{% url 'checkout' %}" method="POST"> {% csrf_token %} <!-- POST到外部URL时禁止使用 --> <button rel="noreferrer" id="checkout-button" class="btn btn-sm" type="submit"> 购买 </button> </form>
我目前有以下疑问:
- 是否有其他方法可以不使用POST实现相同效果?
- 我是否误解了Stripe的文档?
- 该结账URL是否真的属于“外部URL”?
- 我是否应该放弃这种方式,将Stripe结账功能硬编码到网站中以提升安全性?
更新内容
根据要求,我提供修改敏感信息后的详细文件内容。
views.py:
@csrf_exempt def create_checkout_session(request): if request.method == 'POST': try: checkout_session = stripe.checkout.Session.create( line_items=[ { 'price': 'pr_123456789', 'quantity': 1, }, ], mode='payment', success_url=DOMAIN + 'payments/success', cancel_url=DOMAIN + 'payments/cancel', automatic_tax={'enabled': True}, ) except Exception as e: return HttpResponse(f'<h1>{str(e)}</h1>') return redirect(checkout_session.url)
urls.py:
urlpatterns = [ path('', views.home, name='home'), path('checkout/', views.create_checkout_session, name='checkout'), path('success/', views.success, name='success'), path('cancel/', views.cancel, name='cancel'), ]
checkout_session变量接收参数并返回一个对象,我可以通过checkout_session.url获取结账链接(如views.py所示)。
我认为checkout_session.url是外部URL,因为它由Stripe生成并托管。打印出来的结果如下:
https://checkout.stripe.com/c/pay/cs_test_long_hex_variable_relevant_to_my_API_keys
每个由Stripe生成的结账会话对应的URL都是唯一的。
我确信Stripe有自己的安全机制,但我担心的是CSRF保护中的“跨站”部分:
- 引导用户前往外部支付提供商时,安全保障是我的责任吗?
- CSRF保护的工作机制是否真的适用于这种场景?
- 如果适用,Django有没有内置的解决方案?这肯定是一个常见场景。
- Stripe官方文档
- Django官方文档
- Windows 11
- PyCharm IDE
- Python 3.11
- Django 4.2
- Stripe 5.4
- Firefox 114.0.1
我尝试寻找类似的解决方案,找到了一些相关问题,但没有找到针对POST到外部URL的CSRF保护的具体解决方案。
Django文档指出,POST到外部URL时禁止使用{% csrf_token %},但没有进一步说明任何解决方案或替代方案。
在意识到解决方案并不明显后,我认为问题出在我自身对CSRF保护和Web安全知识的缺乏上,因此来到StackOverflow寻求帮助。
内容的提问来源于stack exchange,提问作者roiul

