Django之同源和跨域、CSRF详解

一.CSRF

全称是Cross Site Request Forgery,字面意思是跨站点伪造请求。
攻击者通过HTTP请求江数据传送到服务器,从而盗取回话的cookie。盗取回话cookie之后,攻击者不仅可以获取用户的信息,还可以修改该cookie关联的账户信息。
在这里插入图片描述
原理解释:
为了防止跨站攻击,django中需要在前端页面中添加一个标签:{% csrf_token %},他会在前端页面生成一个input标签,里面有一串随机的字符串,并且后端会在浏览器设置一个cookie信息,一个csrftoken的键值对,每次请求过来的时候,后端会把input标签中的随机字符串和cookie信息拿出来解密判断,页面每次刷新input标签中的随机字符串都会变,而cookie信息里面的csrftoken对应的值不会变。
加密规则解释:
1.会把token前面32位进行加密(加密方法:salt),后面的是加密的token,一共组成的一个随机字符串。
2.django会验证input标签中的token(随机字符串)和cookie中的csrftoken是否能解密出同样的secret key,如果判断成功则本次请求合法。官方文档中说到,检验token时,只比较secret是否和cookie中的secret值一样,而不是比较整个token。
在这里插入图片描述
django加密解密源码:

def _compare_salted_tokens(request_csrf_token, csrf_token):
    # Assume both arguments are sanitized -- that is, strings of
    # length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS.
    return constant_time_compare(
        _unsalt_cipher_token(request_csrf_token),
        _unsalt_cipher_token(csrf_token),
    )

def _unsalt_cipher_token(token):
    """
    Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
    CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt
    the second half to produce the original secret.
    """
    salt = token[:CSRF_SECRET_LENGTH]
    token = token[CSRF_SECRET_LENGTH:]
    chars = CSRF_ALLOWED_CHARS
    pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt))
    secret = ''.join(chars[x - y] for x, y in pairs)  # Note negative values are ok
    return secret

django不是为所有请求都加csrf_token这个标签,它是判断你这个页面的下一个请求会提交数据或者更新数据时会加上这个csrf_token标签,不是所有页面都会返回cookie信息,如果你想强制加可以用一个方法:

from django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie   #强制设置cookie
def  ajax(request):
    if request.method == 'GET':
        return render(request,'middlware/test.html')
    else:
        return HttpResponse('ok')

效果:
在这里插入图片描述

二.ajax通过csrf_token的第三中方式

之前ajax文章中提到过两种方式,第三中今天才更新出来,主要就是要理解csrf_token的工作原理。
之前文章中是添加到请求体中的,data这个参数中,下面是添加到请求头中:
需要注意的地方:
1.请求头中加需要用cookie获取,要用到jQuery的包,下载地址:jquery.cookie.js
2.需要使用headers,不是添加到data中

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>ajax添加csrf_token</h1>
{% csrf_token %}
用户名:<input type="text" id="username">
<button id="btn">确认</button>
<script src="{% static 'jquery.js' %}"></script>
<script src="{% static 'jquery.cookie.js' %}"></script>
<script>
    $('#btn').click(function () {
        $.ajax({
            url:"/app03/ajax/",
            type:'post',
            headers:{"X-CSRFToken":$.cookie('csrftoken')},  第三中方式 在请求头中加
            data:{
                uanme:$('#username').val(),
                //csrfmiddlewaretoken:$("[name=csrfmiddlewaretoken]").val(),  第二种方式  请求体中加
                //csrfmiddlewaretoken:"{{ csrf_token }}",   第一种方式  请求体中加
            },
            success:function (res) {
                console.log(res)
            }
        })
    })
</script>
</body>
</html>

三.同源和跨域

同源:同源(ip地址、协议、端口都相同才是同源),不同源就是跨域
跨域:氛围简单请求跨域和复杂请求跨域
简单请求:
启动两个项目,一个项目端口是8000,另一个是8001。

//8000
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>pro01 base</h1>
<button id="btn">确认</button>

<script src="{% static 'jquery.js' %}"></script>
<script>
   $('#btn').click(function () {
       $.ajax({
           url:'http://127.0.0.1:8001/index/',  #跨域
           #url:'{% url 'index' %}',  #访问自己服务器的路由,同源(ip地址、协议、端口都相同才是同源)
           type:'get',
           contentType:'application/json',
           data:JSON.stringify({'k1':'v1'}),
           success:function (res) {
               console.log(res)
           }
       })
   })
</script>
</body>
</html>

访问http://127.0.0.1:8001/index/这个地址会造成跨域,报错信息如下:

Access to XMLHttpRequest at 'http://127.0.0.1:8001/index/' from 
origin 'http://127.0.0.1:8000' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the 
requested resource.

中文意思:
从“http://127.0.0.1:8001/index/”访问XMLHttpRequest
源“http://127.0.0.1:8000”已被CORS策略阻止:
上不存在“访问控制允许源”头请求的资源。

这个就需要在被请求的8001项目上面设置响应头:

def index(request):
    a = {'name':'zhang'}
    ret = JsonResponse(a)
    # 跨域简单请求
    #加响应头 只要这个域过来的请求不受浏览器的同源策略的拦截
    ret["Access-Control-Allow-Origin"] = "http://127.0.0.1:8000"  
    #只有这个ip和端口来的请求,我才给他数据,其他你浏览器帮我拦着
    # ret["Access-Control-Allow-Origin"] = "*"  #所有域
    return ret

复杂请求:
什么是复杂请求?

1) 请求方法是以下三种方法之一:(也就是说如果你的请求方法是什么put、delete等肯定是非简单请求)
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:(如果比这些请求头多,那么一定是非简单请求)
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、
multipart/form-data、text/plain,也就是说,如果你发送的
application/json格式的数据,那么肯定是非简单请求,vue的axios默认的请求
体信息格式是json的,ajax默认是urlencoded的。

浏览器对这两种请求的处理,是不一样的。

* 简单请求和非简单请求的区别?
   简单请求:一次请求
   非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
   
* 关于“预检”
- 请求方式:OPTIONS
- “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
- 如何“预检”
     => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
        Access-Control-Request-Method
     => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
        Access-Control-Request-Headers

示例:

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>pro01 base</h1>
<button id="btn">确认</button>
<script src="{% static 'jquery.js' %}"></script>
<script>
   $('#btn').click(function () {
       $.ajax({
           url:'http://127.0.0.1:8001/index/',
           type:'get',
           contentType:'application/json',  #加了请求头 复杂请求
           data:JSON.stringify({'k1':'v1'}),  #json数据
           success:function (res) {
               console.log(res)
           }
       })
   })
</script>
</body>
</html>

复杂请求报错:

扫描二维码关注公众号,回复: 11606752 查看本文章
Access to XMLHttpRequest at 'http://127.0.0.1:8001/index/' from origin 
'http://127.0.0.1:8000' has been blocked by CORS policy: 
Request header field content-type is not allowed by Access-Control-Allow-Headers
 in preflight response.
中文意思:
	在“http://127.0.0.1:8001/index/”从源访问XMLHttpRequest
    “http://127.0.0.1:8000”已被CORS策略阻止:
   访问控制允许标题不允许请求标题字段内容类型飞行前的反应。

首先会发一个OPTIONS请求,预检一下后端服务器是否允许我这样的数据,预检通过后才再发送一次请求用于数据传输:
在这里插入图片描述
报错问题解决:

def index(request):
    a = {'name':'zhang'}
    ret = JsonResponse(a)
    ret["Access-Control-Allow-Origin"] = "http://127.0.0.1:8000"  #只有这个ip和端口来的请求,我才给他数据,其他你浏览器帮我拦着
    # ret["Access-Control-Allow-Origin"] = "*"  #所有域

    # 跨域复杂请求  告诉浏览器如果是复杂请求也给我过去
    ret["Access-Control-Allow-Headers"] = 'content-type'  
    return ret

结果,发了两次请求,第一次预检通过,第二次才再一次发送我们的数据:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_39253370/article/details/105684890