我的Django版本是2.2,并且我已经配置httpd.conf(Apache / 2.4.39(Unix)mod_auth_gssapi / 1.6.1 mod_wsgi / 4.6.5 Python / 3.7)来启动进程池,如下所示:
WSGIDaemonProcess my_app processes=6 threads=12 display-name=%{GROUP} home=/tmp maximum-requests=1000 inactivity-timeout=1800 request-timeout=500 python-path=${MY_ENV_VAR}
通过此设置,我在进行POST请求时会收到Forbidden 403 errors
和CSRF verification failed
消息。
如果我不断刷新页面,那么幸运的话,我的POST请求将会成功。现在,我猜测这是因为请求可能已经通过具有“正确缓存”的Django实例降落到了wsgi进程中。
要检验上述理论,如果我将httpd.conf更改为只有如下所示的1个池进程,则不会出现403错误。
WSGIDaemonProcess my_app processes=1 threads=12 display-name=%{GROUP} home=/tmp maximum-requests=1000 inactivity-timeout=1800 request-timeout=500 python-path=${MY_ENV_VAR}
我已按照Django documentation上CSRF的特定说明进行操作,并且在CSRF_USE_SESSIONS
设置为True
或False
时都尝试了这两种情况
如果我打算为wsgi进程启动一个池,而每个进程都将运行我的Django应用程序,则推荐的配置Django的方法是什么?
我进行了一些测试,并确认发生403错误时未调用我的views.py。因此,Django在尝试调用views.py之前就拒绝了POST请求。以下是views.py的代码段:
def my_func(request):
if request.is_ajax() and request.method == 'POST':
# some logic
try:
response = ... # some logic to build response
return HttpResponse(json.dumps(response), content_type="application/json")
except Exception as e:
return HttpResponseBadRequest('\n'.join(errors))
return render(request, 'my_app/my_template.html', locals())
使用的模板具有简单的形式:
<form class="form-horizontal" role="form">{% csrf_token %}
要提交表单,请进行AJAX调用:
$.ajax({
type: "post",
url: ".",
data: data,
cache: false,
processData: false,
contentType: false,
success: function(response) {
// some code
},
error: function(xhr, status, e) {
// some code
}
});
这是我的httpd.conf的摘录
LimitRequestFieldSize 50000
LimitRequestLine 50000
TimeOut 600
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule alias_module modules/mod_alias.so
LoadModule auth_gssapi_module modules/mod_auth_gssapi.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule filter_module modules/mod_filter.so
LoadModule info_module modules/mod_info.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule mime_module modules/mod_mime.so
LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule status_module modules/mod_status.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule wsgi_module modules/mod_wsgi.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule env_module modules/mod_env.so
LoadModule include_module modules/mod_include.so
WSGISocketPrefix ${MY_SOCKET_PREFIX}
WSGILazyInitialization On
WSGIRestrictEmbedded On
WSGIScriptReloading On
WSGIDaemonProcess my_app processes=6 threads=12 display-name=%{GROUP} home=/tmp maximum-requests=1000 inactivity-timeout=1800 request-timeout=500 python-path=${HTTPD_my_app_PYTHONPATH}
#If I use the below that has processes=1 instead I do not get CSRF verification failure
#WSGIDaemonProcess my_app processes=1 threads=12 display-name=%{GROUP} home=/tmp maximum-requests=1000 inactivity-timeout=1800 request-timeout=500 python-path=${HTTPD_my_app_PYTHONPATH}
Listen ${HTTPD_my_app_PORT}
<VirtualHost *:${HTTPD_my_app_PORT}>
Redirect / http://${MY_HOST}:${HTTPD_my_app_PORT_REDIRECT}/
</VirtualHost>
Listen ${HTTPD_my_app_PORT_REDIRECT}
<VirtualHost *:${HTTPD_my_app_PORT_REDIRECT}>
WSGIScriptAlias / ${HTTPD_my_app_BASE_DIR}/my_app_dir/wsgi.py
WSGIProcessGroup my_app
WSGIApplicationGroup %{GLOBAL}
<Directory "${HTTPD_my_app_BASE_DIR}/my_app_dir/">
<Files wsgi.py>
AuthType GSSAPI
AuthName "MY_ENTITY/GSSAPI"
GssapiCredStore keytab:${MY_KEYTAB}
GssapiAllowedMech krb5
GssapiPublishErrors On
SetHandler wsgi-script
Require valid-user
</Files>
</Directory>
<Directory ${HTTPD_my_app_BASE_DIR}/my_app_dir/media>
Require all granted
</Directory>
<Directory ${HTTPD_my_app_BASE_DIR}/static_files>
Require all granted
</Directory>
</VirtualHost>
答案 0 :(得分:1)
事实证明,该问题是由于在SECRET_KEY
文件中将以编程方式生成的随机值分配给了settings.py
的缘故-本质上是在settings.py
中完成的:
SECRET_KEY = generate_a_random_string()
非常感谢Alasdair,我再次仔细查看了how Django does CRSF verification上的官方文档,并特别注意了以下几点:
一个隐藏的表单字段,名称为“ csrfmiddlewaretoken” 外发POST表单。同样,此字段的值是 秘诀,并在其中添加了盐并用于加扰 它。每次调用get_token()都会重新生成盐,以便 每个此类响应中都会更改表单字段值。
对于所有未使用HTTP GET,HEAD,OPTIONS的传入请求 或TRACE,必须存在CSRF Cookie,并且“ csrfmiddlewaretoken” 该字段必须存在且正确。如果不是,则用户将获得一个 403错误。
验证“ csrfmiddlewaretoken”字段值时,仅 (不是完整令牌)与Cookie中的秘密进行比较 值。这允许使用不断变化的令牌。虽然每个请求 可能会使用自己的令牌,这个秘密仍然是所有人的共同点。
此检查由CsrfViewMiddleware 完成。
所以我的问题是Apache正在分解多个wsgi Django实例/进程,但是每个实例都有一个不同的SECRET_KEY
。因此,尽管Django并没有真正跟踪完整的令牌,但是Django实例确实需要知道/拥有对应的秘密密钥,该密钥与一些不可食用的盐一起构成了令牌。
因此,解决该问题的方法是使SECRET_KEY
与Apache启动的所有Django进程相同。