使用Python Requests传递csrftoken

65

如何使用Python模块Requests传递csrftoken?这是我的代码,但它没有起作用,我不确定应该将它传递给哪个参数(data、headers、auth...)

import requests
from bs4 import BeautifulSoup

URL = 'https://portal.bitcasa.com/login'

client = requests.session(config={'verbose': sys.stderr})

# Retrieve the CSRF token first
soup = BeautifulSoup(client.get('https://portal.bitcasa.com/login').content)
csrftoken = soup.find('input', dict(name='csrfmiddlewaretoken'))['value']

login_data = dict(username=EMAIL, password=PASSWORD, csrfmiddlewaretoken=csrftoken)
r = client.post(URL, data=login_data, headers={"Referer": "foo"})

每次都是相同的错误信息。

<h1>Forbidden <span>(403)</span></h1>
<p>CSRF verification failed. Request aborted.</p>

还有什么其他的内容被发布了吗?只有 usernamepasswordcsrfmiddlewaretokennext 吗?或者除此之外还有其他字段吗?当您将 next='/' 添加到您的 login_data 字典中时会发生什么? - Martijn Pieters
这就是所有发布的内容。将 next='/' 设置为相同的错误。 - Jeff
请注意:您可以跳过整个BeautifulSoup解析,直接从cookie中获取csrf令牌;运行client.get但不进行解析,只需使用value = client.cookies ['csrftoken']。否则,无法确定。 - Martijn Pieters
是的,我刚刚弄明白了。我把Referer改成了URL,然后它就神奇地工作了。不过我不确定为什么会这样。我得去研究一下。非常感谢你的帮助Martijn。 - Jeff
1
因为CSRF检查代码首先检查referrer,然后才是CSRF令牌。我以为错误消息会可见,但除非服务器处于调试模式,否则不会显示,这一点最初让我感到困惑,不知道为什么代码不能工作。然后我自己尝试了一下,发现了同样的错误,并返回referrer,它必须与主机名匹配。 - Martijn Pieters
显示剩余7条评论
2个回答

119

如果您要设置引荐头部(referrer header),那么对于该特定站点,您需要将引荐设置为与登录页面相同的URL:

import sys
import requests

URL = 'https://portal.bitcasa.com/login'

client = requests.session()

# Retrieve the CSRF token first
client.get(URL)  # sets cookie
if 'csrftoken' in client.cookies:
    # Django 1.6 and up
    csrftoken = client.cookies['csrftoken']
else:
    # older versions
    csrftoken = client.cookies['csrf']

login_data = dict(username=EMAIL, password=PASSWORD, csrfmiddlewaretoken=csrftoken, next='/')
r = client.post(URL, data=login_data, headers=dict(Referer=URL))

在使用不安全的http时,通常会过滤掉Referer标头,并且很容易被欺骗,因此大多数网站不再需要设置该标头。但是,在使用SSL连接并且设置了该标头时,对于站点验证至少引用了可以逻辑上发起请求的内容是有意义的。Django在加密连接(使用https://)时执行此操作,并要求必须执行。


3
如果每个HTTP请求都生成CSRF,会发生什么?上述方法是否仍然有效? - py_ios_dev
这个能和任何类型的服务器一起使用,还是只能和Django一起使用? - loxaxs
@loxaxs:这不是Django特定的,而是取决于具体的服务器期望。 - Martijn Pieters

4
类似地,使用 django 的 csrf_client 时,需要注意的主要区别在于在 login_data 中使用 csrftoken.value。已经测试过可以用于 Django 1.10.5 --
import sys

import django
from django.middleware.csrf import CsrfViewMiddleware, get_token
from django.test import Client

django.setup()
csrf_client = Client(enforce_csrf_checks=True)

URL = 'http://127.0.0.1/auth/login'
EMAIL= 'test-user@test.com'
PASSWORD= 'XXXX'

# Retrieve the CSRF token first
csrf_client.get(URL)  # sets cookie
csrftoken = csrf_client.cookies['csrftoken']

login_data = dict(username=EMAIL, password=PASSWORD, csrfmiddlewaretoken=csrftoken.value, next='/')
r = csrf_client.post(URL, data=login_data, headers=dict(Referer=URL))

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