问题描述
我正在使用 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)
根据需要取消注释和评论以测试第二个解决方案。