Context

我正在建立一个使用Django框架和条带的用户支付在Python的网站.我目前正处于本地开发的测试/调试阶段,离生产版本还很远.

目前在用CSRF保护砖墙,也找不到解决方案.我的收银台在没有CSRF保护的情况下工作得很好.

这里还有其他非常类似的话题,但由于我对CSRF保护本身缺乏了解,我希望有人能告诉我在我的特定场景中应该做些什么.

这种情况应该有一个通用的实践解决方案,但我已经研究了Django和Strip的大部分文档,但没有成功.

欢迎批评,这是我的第一篇StackOverflow帖子.


The Problem

我有一个查看功能,使用户能够在我的网站上通过发送到Stripe外部 checkout 页面购买产品.

Views.py(过时)

@csrf_exempt # CSRF Protection disabled for testing
def create_checkout_session(request):
    if request.method == 'POST':
        try:
            checkout_session = stripe.checkout.Session.create(

                ...

            )
        except Exception as e:

            ...

        return redirect(签出_会话.url)

在测试期间,我使用Django的@csrf_exempt修饰器禁用了查看功能的CSRF保护.我这样做是为了绕过"403 CSRF验证失败"错误.

我之所以得到这个错误,首先是因为Django's Documentation上的this statement:

<form method="post">{% csrf_token %}

不应对以外部URL为目标的POST表单执行此操作,因为这会导致CSRF令牌泄漏,从而导致漏洞.

在删除@csrf_exempt修饰符之后,我现在被卡住了,因为这正是我的提交表单的工作方式,如下面的Products.html中所示, checkout URL是由stripe生成的(如views.py中所示),因此(我认为)是一个外部URL:

Products.html

<form rel="noreferrer" action="{% url 'checkout' %}" method="POST">
    {% csrf_token %} <!-- forbidden in POST to external URL -->
    <button rel="noreferrer" id="checkout-button" class="btn btn-sm" type="submit">
        Buy
    </button>
</form>

目前正在质疑以下几件事:

  • 有没有其他方法可以在不使用POST的情况下获得相同的结果?
  • 我是不是误解了STRIP文档?
  • checkout URL实际上是"外部的"吗?
  • 为了更好的安全性,我是否应该放弃这种方法,并在我的网站上硬编码条纹的 checkout ?

UPDATE

根据要求,在对敏感信息进行一些更改后,下面是详细的文件.

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(签出_会话.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接受参数并返回一个对象,我可以在其中调用签出_会话.url,如views.py中所示.

我的印象是check out_ession.url是一个外部URL,这是因为Strip生成并托管了这个链接.为了确认这一点,我在check out_ession.url上运行了一次控制台打印,它返回以下内容:

签出_会话.url

https://checkout.stripe.com/c/pay/cs_test_long_hex_variable_relevant_to_my_API_keys

此URL对于由条纹生成的每个 checkout 会话都是唯一的.

我确信Stripe有它自己的安全性,但我担心的是CSRF Protection的跨站点部分.

  • 我有责任为用户维护外部支付Provider 的安全吗?
  • 这就是CSRF保护的工作方式吗?
  • 如果是这样的话,Django的内置解决方案是什么,因为这必须是一种常见的做法?

使用的资源:

What I've Tried:

我试图找到类似的解决方案,我在这里发现了与我的问题相关的多个问题,但我发现没有一个涉及CSRF保护外部URL的细节.

Django文档指出,禁止在POST期间使用{%CSRF_TOKE%}到外部URL,但在进一步调查后,没有详细说明任何解决办法或替代方案.

在得出解决方案并不明显的结论后,我的印象是,我的问题不是别人造成的,因为我缺乏CSRF防护和一般网络安全方面的知识.这就把我带到了StackOverflow的知识池中.

推荐答案

我假设你在urls.py年内会有这样的东西:

urlpatterns = [
  // ...
  path('checkout', views.create_checkout_session, name='checkout'),
]

因此,当您的模板中有这样的表单时:

<form rel="noreferrer" action="{% url 'checkout' %}" method="POST">

点击提交按钮will post to your view,而不是某个外部URL.这意味着您应该保留CSRF令牌,而不是使用@csrf_exempt修饰符.

另一方面,如果您的表单直接使用条带URL,如下所示:

<form rel="noreferrer" action="https://stripe.com/api/checkout" method="POST">

那么您不希望包含CSRF令牌,因为表单会将其直接发送到外部URL.

在您的示例中,CSRF令牌被发送回您自己的视图,因此不存在安全问题.然后,该视图具有向Stripe发送请求的代码.只需确保不在代码中包含CSRF令牌即可.

Python-3.x相关问答推荐

如何在选中项目时设置QTreeView中整行的 colored颜色 ?

在一行中读写一个csv文件

在 Python 中实现 COM 接口

无法使用 curve_fit() 在 python 中复制高斯函数的曲线拟合

为什么 mypy 不适用于 sqlalchemy?

使用 OpenCV 从图像中减go 一条线

有效地缩短列表,直到第一次和最后一次出现不同于 None 的值

非拉丁字符的Python正则表达式不起作用

为什么最简单的流光示例会出错?

每个数据行中每个数据帧值的总和

是否将dict转换为一个数据帧,每个值都有重复的键?

从 Python2 到 Python3 的这种解包行为的变化是什么?

Python3 mysqlclient-1.3.6(又名 PyMySQL)的用法?

如何在 Python 中计算 cohen 的 d?

作为函数对象属性的 __kwdefaults__ 有什么用?

无法在 Windows 8 中使用 Python 3.3 找到 vcvarsall.bat

接收导入错误:没有名为 *** 的模块,但有 __init__.py

TypeError:多个基地有实例布局冲突

Selenium Python - 处理没有这样的元素异常

无法解码 Python Web 请求