django 1.6版本POST请求整理

2016/5/30 posted in  python

django 发送POST请求

django发送post请求,如果直接发送,会出现csrf cookie not set 报错以及403错误。网上各种解决方法也比较多。

最靠谱的方法是引入csrf_exempt装饰器,如下既能解决。

    from django.views.decorators.csrf import csrf_exempt
    @csrf_exempt
    def XXX(req):
        return ……

为什么要使用csrf_exempt装饰器

默认情况下,csrf的中间件是通过该方式被全局开启的,对所有的POST生效。可以在看默认的配置

    MIDDLEWARE_CLASSES = [
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.locale.LocaleMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'pagination.middleware.PaginationMiddleware',
    ]

这里的django.middleware.csrf.CsrfViewMiddleware定义了csrf的中间件。

定义这个中间件的目的是整理POST请求的模拟安全,我们需要在post请求体中带上token,防止post请求的仿冒。

那么我们可以采用这种csrf_exempt的方式来进行,但是这种方式是不安全的,因为会去掉了token机制,在服务端不进行token的验证,我们使用下面的方法。

在form中带上{% csrf_token %}

配置如下所示:

    <form id="contact" action="{% url 'blog:sendmail' %}">
      <fieldset>
         {% csrf_token %}
          <span class="error" id="name_error">Please enter name !</span>
          <span class="error" id="email_error">Please enter email address !</span>
          <span class="error" id="email_error2">Please enter valid email address !</span>
          <span class="error" id="msg_error">Please enter message !</span>
          <label for="name" id="name_label">Name (required)</label>
          <input type="text" name="name" id="name" size="50" value="" class="text-input" />
          <label for="email" id="email_label">Email (required)</label>
          <input type="text" name="email" id="email" size="50" value="" class="text-input" />
          <label for="subject" id="subject_label">Subject</label>
          <input type="text" name="subject" id="subject" size="50"  value="" class="text-input" />
          <label for="msg" id="msg_label">Message</label>
          <textarea rows="10" name="msg" id="msg" class="text-input"></textarea>
          <br />
          <input type="submit" name="submit" class="button" id="submit_btn" value="Send Message"/>
      </fieldset>
    </form>

在form中带上csrf_token后会在其中生成一个token。

可以查看源文件看出,其中是这样解析的

<input type="hidden" name="csrfmiddlewaretoken" value="g8GAWhzYB3HGZPwHiVGEtoAkagVu6EtW">

我们可以看到这个带上了相应的token,放在隐藏的iuput标签中,所以如果是form表单直接使用的话,可以加上这个标签就可以了,如果使用ajax发送的话,需要在data里面加上相应数据。

var dataString = 'name='+ name + '&email=' + email + '&subject=' + subject + '&msg=' + msg + '&csrfmiddlewaretoken=' + csrfmiddlewaretoken +;
    //alert (dataString);return false;
    
  jQuery.ajax({
  type: "POST",
  url: "sendmail",
  data: dataString,
  success: function() {
    jQuery('#contactform').html("<div id='message' class='ten columns'></div>");
    jQuery('#message').html("<strong>Contact Form Submitted!</strong>")
    .append("<p>We will be in touch soon.</p>")
    .hide()
    .fadeIn(1500, function() {
      jQuery('#message');
    });
  }
 });

此时去掉csrf_exempt装饰器也可以请求通过了。

在生成表单的页面中生成csrf

在生成表单中的view中生成csrf:

    c = csrf(request)       
    result_dict = {}        
    result_dict.update(c)       
    return render(request, 'csrf/index.html', result_dict)

对于POST请求,可以在目标view方法中对于post请求方法进行限制,否则可能出现服务器内部错误

在view中限制post请求:

    from django.views.decorators.http import require_http_methods
    @require_http_methods(["POST", ])

django的CsrfViewMiddleware原理

Django的实现并不是基于后端的,Django不会在服务端记录会话和对应的token值,而是在初次设置token的时候同时设置一个名为csrftoken的永久Cookie,并且Cookie的值与表单中csrfmiddlewaretoke的值相同。

提交之后,Django通过Cookie和提交的数据进行判断。

具有该Cookie之后,Django将不会每次请求都生成token,而是根据Cookie直接向前端返回token

在csrf中间件全局开启的情况下,对于使用POST方法打开的页面,并且提交的内容没有重要性、没有机密性的可以通过csrf_exempt来关闭

在views中导入csrf_exempt装饰器

    from django.views.decorators.csrf import csrf_exempt

在函数前使用

    @csrf_exempt

对于csrf中间件全局关闭的情况下,对少量页面开启csrf防护可以通过csrf_protect来开启

在views中导入csrf_protect装饰器

    from django.views.decorators.csrf import csrf_protect

在函数前使用

    @csrf_protect

可以自行检测csrf

通过检测referer实现

    referrer = request.META.get('HTTP_REFERER')

通过将referrer与业务流程上正常的referer进行比较,处理非正常referer的请求

    @require_http_methods(['POST', ])
    @csrf_exempt
    def refer_view(request):
    referrer = request.META.get('HTTP_REFERER')
    mail = request.POST.get('mail')
    if referrer != "http://127.0.0.1:8000/test_csrf/" or mail is None or mail == '':
        return HttpResponseRedirect("/test_csrf/")
    return render(request, 'csrf/refer.html', {'result': mail})

Ajax中的csrf

AJAX中的CSRF与普通的使用差别不大,但是有两种实现方式

一种就是利用Django的CSRF实现的原理,利用前端脚本从Cookie中读取csrftoken的值并放在data中提交。

另一种就是在template中直接写入token,直接从template定义data中的tokenmiddlewaretoken字段。

    //方式一:运行getCookie函数,从Cookie中读取token值
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    var csrftoken = getCookie('csrftoken');
    input_data = "mail=" + $("#mail_input").val() + "&csrfmiddlewaretoken=" + csrftoken;
    //方式二:在模版中写入csrf_token
    
    input_data = "mail=" + $("#mail_input").val() + "&csrfmiddlewaretoken=" + "{{ csrf_token }}";
    //ajax在使用的时候与平常相同
    $.ajax({
        url:'/test_csrf/ajax_api/',
        data:input_data,
        type:'post',
        dateType:'text',
        success:function(data){
            $("#ajax_return").val(data);
        },
        error: function () {
            alert("error");
        }
    })

在进行代码审查的时候,需要审查的settings.py中是否添加了'django.middleware.csrf.CsrfViewMiddleware',并且涉及POST请求的view中是否没有使用@csrf_exempt

另外,如果传输的数据非常重要,建议增加其他校验方式如手机验证码、图片验证码或其他第三方因素进行校验。