如何将数据 POST 到一个通用的 django 视图,它有什么好的方法

问题描述

我正在使用 python 和 urllib 模块将我的凭据POST 到用于登录的通用 django 视图,仅此而已。我尝试了一些基于标准基本身份验证模式的东西,修改了身份验证标头......但都是一样的,它一直向我抛出 403 禁止代码。 我搜索解决这个问题的答案,但对我来说......我找不到任何答案。

代码

from django.contrib.auth import views
urlpatterns = [
    path('accounts/login/',views.LoginView.as_view(template_name='accounts/login.html'),name='login'),]

settings.py

"""
Django settings for tests project.

Generated by 'django-admin startproject' using Django 2.2.17.

For more @R_976_4045@ion on this file,see
https://docs.djangoproject.com/en/2.2/topics/settings/

For the full list of settings and their values,see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR,...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/

# Security WARNING: keep the secret key used in production secret!
SECRET_KEY = 'ukqp@#s^bo)wcl)qr#fc1%+-1rm(f1-6(8trin3rl)6=dbwt@9'

# Security WARNING: don't run with debug turned on in production!
DEBUG = True
LOGIN_REDIRECT_URL = '/catalog/'
ALLOWED_HOSTS = []


# Application deFinition

INSTALLED_APPS = [
    'django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','catalog.apps.CatalogConfig','accounts.apps.AccountsConfig',]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',]

ROOT_URLconf = 'tests.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates','Dirs': [],'APP_Dirs': True,'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages','catalog.context_processors.muelte',],},]

STATICFILES_Dirs = [
    os.path.join(BASE_DIR,'static'),]

Wsgi_APPLICATION = 'tests.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3','NAME': os.path.join(BASE_DIR,'db.sqlite3'),}
}


# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',{
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',{
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',{
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',]


# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS,JavaScript,Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/

STATIC_URL = '/static/

' 我的尝试

import urllib.parse,urllib.request
base = 'http://localhost:8000/'
login_path = 'accounts/login/'
blurl = urllib.parse.urljoin(base,login_path)
headers = {
    'username': 'l0new0lf','password': 'ColdSOul',}
rq = urllib.request.Request(blurl,method='post',headers=headers)
print(urllib.request.urlopen(rq))

追溯:

Traceback (most recent call last):
  File "/home/l0new0lf/Desktop/algo.py",line 11,in <module>
    print(urllib.request.urlopen(rq))
  File "/usr/lib/python3.8/urllib/request.py",line 222,in urlopen
    return opener.open(url,data,timeout)
  File "/usr/lib/python3.8/urllib/request.py",line 531,in open
    response = meth(req,response)
  File "/usr/lib/python3.8/urllib/request.py",line 640,in http_response
    response = self.parent.error(
  File "/usr/lib/python3.8/urllib/request.py",line 569,in error
    return self._call_chain(*args)
  File "/usr/lib/python3.8/urllib/request.py",line 502,in _call_chain
    result = func(*args)
  File "/usr/lib/python3.8/urllib/request.py",line 649,in http_error_default
    raise HTTPError(req.full_url,code,msg,hdrs,fp)
urllib.error.HTTPError: HTTP Error 403: Forbidden

####编辑 1####

我注意到我缺少 csrf 令牌, 将它添加到 POST 标头,但仍然出现相同的错误。虽然这种方法行不通,因为 AFAIK 每个 csrf 令牌都是为每个请求独立生成的。

import urllib.parse,urllib.request
from bs4 import BeautifulSoup
base = 'http://localhost:8000/'
login_path = 'accounts/login/'
blurl = urllib.parse.urljoin(base,login_path)
headers = {
    
    'username': 'l0new0lf',}
get_rq = urllib.request.urlopen(blurl)
soup = BeautifulSoup(get_rq.read(),features='lxml')
form = soup.select_one('form')
csrf = form.select_one('form input[name^="csrf"]')
headers.update(((csrf['name'],csrf['value']),))
rq = urllib.request.Request(blurl,headers=headers)
print(urllib.request.urlopen(rq))

PD: 我不仅在身份验证时遇到这个问题,而且在将任何数据发布到任何视图时也会遇到这个问题。此外,我也接受基于请求模块的答案,如果更多方便。

非常感谢。

解决方法

默认情况下,Django 视图具有 CSRF 令牌 checking

案例 1. 检查此答案 here 以使您的 api 测试代码正常工作

案例 2。如果您想删除此功能,请使用函数 csrf_exempt

    urlpatterns = [
    path('accounts/login/',csrf_exempt(views.LoginView.as_view(template_name='accounts/login.html'),name='login')),]  

案例 3. 尝试在您的登录页面表单中进行测试,如 here

如果您仍然面临 403 问题,请仔细检查您的登录凭据

,

我询问了一些关于 csrf 是什么,以及如何与使用这种安全措施的网络服务器通信(现在有什么像样的网络服务器没有这个?),感谢@RenjithThankachan 的回答,这给了我启发,AFAIK只有两种方法可以做到。

传统方式和第一种方式:

我意识到需要将“csrfmiddlewaretoken”及其各自的值以 key=value 格式添加到 POST 正文,也如一个 header 只是这次它的类型只是“csrftoken”,否则它不会工作

通过标题和第二种方式: 这是在您选择执行 XMLHtppRequest 的情况下,Django 在文档中声明可以指定一个带有“X-CSRFToken”作为其名称的标头,因此您可以摆脱用 csrf 令牌填充 POST 主体的麻烦.就像下面所说的那样。

AJAX

虽然上述方法可用于 AJAX POST 请求,但它有一些 不便:您必须记住将CSRF令牌作为POST传递 每个 POST 请求的数据。为此,有一个替代方案 方法:在每个 XMLHttpRequest 上,设置一个自定义的 X-CSRFToken 标头(如 由 CSRF_HEADER_NAME 设置指定)到 CSRF 的值 令牌。这通常更容易,因为许多 JavaScript 框架提供 允许在每个请求上设置标头的钩子。

以代码表示:

import urllib.parse,urllib.request
from bs4 import BeautifulSoup
import requests

base = 'http://localhost:8000/'
login_path = 'accounts/login/'
blurl = urllib.parse.urljoin(base,login_path)
get = urllib.request.urlopen(blurl)
form = {
    'username': 'l0new0lf','password': 'DonTMind',#PASS CSRF TOKEN THROUGH POST BODY,THIS IS THE USUAL APPROACH
    'csrfmiddlewaretoken' : get.headers['set-cookie'].split('=',maxsplit=1)[1].split(';',maxsplit=1)[0],}

csrfcookie = get.headers['set-cookie']
data = bytearray()
first = True

for item in reversed(form.items()):
    if not first:
        data += b'&'
    else:
        first = False
    data += item[0].encode('ascii') + b'=' + item[1].encode('ascii')
data = bytes(data)

rq = urllib.request.Request(blurl,data=data,method='post')
rq.add_header('Cookie',csrfcookie)
rq.add_header('Content-Type','application/x-www-form-urlencoded')
rq.add_header('Content-Length',str(len(data)))
#PASS CSRF TOKEN THROGH AN HTTP HEADER,this is better since we don't need to be careful about passing it in to the body each time we make a post request.
#rq.add_header('X-CSRFToken',csrfcookie)

try:
    urllib.request.urlopen(rq)
except Exception as e:
    print(e.status != 403)

根据需要取消注释和评论以测试第二个解决方案。