CSRF验证失败,请求中止 - 作为服务器端包含的Django模板
我花了几个小时在这个问题上。对于Django我还比较新,需要帮助,因为我的设置似乎不太常见。
我用Nginx和UWSGI来提供我的网站,大部分页面是静态的,并不是通过Django来提供的。我使用Django的唯一原因是为了实现一个表单。因此,我决定把表单模板嵌入到一个现有的HTML页面中的一个<div>里,而这个HTML页面是在Django服务器之外的。当DEBUG设置为True时,表单工作得很好,但当设置为False时,表单虽然能显示出来,但在提交时会报错。错误信息是“禁止访问(403)CSRF验证失败。请求被中止。失败原因:CSRF cookie未设置。”我找到的解决方案都假设整个网站都是由Django提供的。然而对我来说,只有一小部分Django模板是通过Nginx的SSI来提供的。我在想这个问题出在哪里,社区能否帮我解决。提前谢谢大家!
HTML outside of Django:
<div id="formContainer"><!--#include virtual="/webform/contact/" --></div>
#Django view
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): #All validation rules pass
# Process the data in form.cleaned_data
firstName = form.cleaned_data['firstName']
lastName = form.cleaned_data['lastName']
street = form.cleaned_data['street']
city = form.cleaned_data['city']
state = form.cleaned_data['state']
zipcode = form.cleaned_data['zipcode']
phone = form.cleaned_data['phone']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
request.session['fname_ses'] = firstName
request.session['lname_ses'] = lastName
request.session['street_ses'] = street
request.session['city_ses'] = city
request.session['state_ses'] = state
request.session['zip_ses'] = zipcode
request.session['phone_ses'] = phone
request.session['email_ses'] = email
request.session['phone_ses'] = phone
request.session['email_ses'] = email
request.session['message_ses'] = message
subject = "A message for _____ from %s %s" %(firstName, lastName)
message_body = ("Here is the personal information provided by the client: \n"
" Full name: %s %s \n"
" Address: %s, %s, %s %s \n"
" Phone: %s \n"
" Email: %s \n \n"
"Message to you: \n \n%s"
%(firstName, lastName, street, city, state, zipcode, phone, email, message)
)
sender = "email1@example.com"
recipients = [
'email1@example.com',
'email2@example.com',
email3@example.com',
]
from django.core.mail import send_mail
send_mail(subject, message_body, sender,
recipients, fail_silently=False)
#Redirect after POST
#reverse() redirects straight to the view instead of the URL
#Use reverse() to prevent URL from appending to current address
return HttpResponseRedirect('webform/thanks_simple.html')
#return HttpResponseRedirect(reverse('webform:thanks'))
else:
form = ContactForm() # An unbound form
return render(request, 'webform/contact.html', {'form': form,})
#contact.html within Django
<p>We welcome your questions & comments</p>
<p>Please fill out the form below and hit 'Submit'</p>
<form action="{% url 'webform:contact' %}" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
<table>
<tr><th><label for="id_firstName">First Name:</label></th><td>{{ form.firstName.errors }} {{ form.firstName }}</td></tr>
<tr><th><label for="id_lastName">Last Name:</label></th><td>{{ form.lastName.errors }} {{ form.lastName }}</td></tr>
<tr><th><label for="id_street">Street:</label></th><td>{{ form.street.errors }} {{ form.street }}</td></tr>
<tr><th><label for="id_city">City:</label></th><td>{{ form.city.errors }} {{ form.city }}</td></tr>
<tr><th><label for="id_state">State:</label></th><td>{{ form.state.errors }} {{ form.state }}</td></tr>
<tr><th><label for="id_zipcode">Zipcode:</label></th><td>{{ form.zipcode.errors }} {{ form.zipcode }}</td></tr>
<tr><th><label for="id_phone">Phone:</label></th><td>{{ form.phone.errors }} {{ form.phone }}</td></tr>
<tr><th><label for="id_email">Email:</label></th><td>{{ form.email.errors }} {{ form.email }}</td></tr>
<tr><th><label for="id_message">Message:</label></th><td>{{ form.message.errors }} {{ form.message }}</td></tr>
</table>
<p><input type="submit" value="Submit" /></p>
</form>
更新:Django返回403错误 -- “CSRF cookie未设置”
我发现了一个有效的解决方案,决定在这些视图上禁用CSRF保护。我会继续测试,看看能否再次破坏网站。一旦完成,我会认为csrf_exempt
是解决方案。理想情况下,保持保护是最好的,但我使用的表单其实并不需要这个保护,而且我已经在这个问题上耗费了太长时间。
2 个回答
我觉得问题的原因是缓存的页面没有返回csrf令牌的cookie,所以csrf中间件在找不到它的时候就报错了。一个明显的解决办法是使用JavaScript,也就是发送一个ajax请求到服务器,如果没有csrf令牌,就让服务器返回一个。成功后,JavaScript会从响应头中获取csrf令牌,具体可以查看文档:https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax,然后更新页面上所有表单(我每个页面上有好几个)的csrfmiddlewaretoken输入值为新的csrf令牌。这只需要做一次,因为下次csrf令牌会保存在cookie里。
希望有更好的解决办法,但这个方法是可行的。
这是一个Django模板的示例代码,用于你的表单视图。记得在表单标签内加上{% csrf_token %}。
<form action="/webform/contact/" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="{% trans 'Create' %}" />
</form>