CSRF验证失败,请求中止 - 作为服务器端包含的Django模板

0 投票
2 回答
786 浏览
提问于 2025-04-18 00:53

我花了几个小时在这个问题上。对于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 个回答

0

我觉得问题的原因是缓存的页面没有返回csrf令牌的cookie,所以csrf中间件在找不到它的时候就报错了。一个明显的解决办法是使用JavaScript,也就是发送一个ajax请求到服务器,如果没有csrf令牌,就让服务器返回一个。成功后,JavaScript会从响应头中获取csrf令牌,具体可以查看文档:https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax,然后更新页面上所有表单(我每个页面上有好几个)的csrfmiddlewaretoken输入值为新的csrf令牌。这只需要做一次,因为下次csrf令牌会保存在cookie里。

希望有更好的解决办法,但这个方法是可行的。

0

这是一个Django模板的示例代码,用于你的表单视图。记得在表单标签内加上{% csrf_token %}。

<form action="/webform/contact/" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="{% trans 'Create' %}" />
</form>

撰写回答