python django csrf全网最详细解释

今天小编为大家带来django中csrf的使用,我们要先知道什么是csrf攻击(跨站请求伪造).在此之前我们要了解简单请求和复杂请求,这俩种请求和跨域有关,在前端产生跨域的时候,那么发送的产生跨域的请求,后端服务器到底执行没有?

要解决上述的问题,我们一步一步来解答,我们通过ajax来发送请求的时候,不知道你有没有发现,有时候会发送两个同样的请求,有时候只发送一个请求.其实,如果发送的请求是复杂请求的话,会先发送一次预检验请求,这次请求服务器接受到后并不会真的执行,而是返回后端可以接受的域名,也就是让浏览器知道这次请求是否可以跨域访问,如果后端返回的是不可以跨域访问,那么真正的请求就不会被浏览器发送出去,更不会被执行.既然如此,那么简单请求和复杂请求恰恰相反,它只会发一次请求,服务端会执行,但是如果产生跨域浏览器应该还是无法解析,但是注意!!!(这个简单请求如果参数正确,身份验证通过,请求方法,api正确......)服务器端是会按照这次请求执行相应代码的.我们默认后端是不允许跨域的或者只允许某一个已经部署的前端程序访问后端,应为这样是比较安全的,如果后端允许任何主机访问服务器,那么无论是简单请求还是复杂请求都会产生csrf攻击,这更加不安全了.所以后端对请求的方法进行限制并且不允许跨域或者只允许某个域可以访问后端,我们只需要解决简单请求的csrf攻击即可.

那么简单请求和复杂请求怎么区分呢?简单请求需要满足四个条件:

1、请求方法是以下三种方法之一:GET、POST、HEAD。

2、不能自定义请求头header,不得人为设置该集合之外的其他首部字段。该集合为:Accept、Accept-Language、Content-Language、Content-Type

3、Content-Type 的值仅限于下列三者之一:

  • text/plain
  • multipart/form-data
  • application/x-www-http-urlencoded

4、请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问.

我们知道了简单请求之后,复杂请求就是除了简单请求之外的请求.

我们了解这两种请求之后就可以真正开始了解什么是csrf攻击了,假设用户在在某个购物平台购买了一个商品,这个网站向服务器端一个余额api发送一个简单请求并携带了正确参数,(默认后端不允许跨域或者只允许某一个已经部署的前端程序访问)后端执行相应代码减少用户余额.这是一个完整的流程.

如果用户此时还没有退出登录,cookie里面的token令牌还没过期清除,用户访问一个钓鱼网站,如果这个钓鱼网站知道这个购物平台的余额api并且携带正确的参数发送请求,同时使用用户尚未退出的toekn令牌进行身份验证.如果购物平台的后端没有csrf保护,这个钓鱼网站就会成功完成操作.同时如果购物平台的后端禁止跨域的话,钓鱼网站就只能发送简单请求来避开跨域.

csrf保护有很多中方法,但是在这里我们只说django中的csrf保护.因为基本跨站伪造请求都是表单请求,所以django采用在发送请求的时候在请求头里添加特殊请求头,由中间件django.middleware.csrf.CsrfViewMiddleware和发送的cookie里面的crsftoken比较是否相等,相等即可通过,不相同不可通过.

首先我们在django中settings.py的中间件中启用django.middleware.csrf.CsrfViewMiddleware:

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    #开启csrfviewmiddleware中间件
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

这是全局启用了django.middleware.csrf.CsrfViewMiddleware这个中间件.

在urls中建立接口:

urlpatterns = [

    path('csrf_token/', views.csrf_detection, name='csrf_detection'),
    path('csrf_token1/', views.csrf_detection1, name='csrf_detection1')
]

建立视图函数:

def csrf_detection(request):
      return HttpResponse('detection')

def csrf_detection1(request):
    return HttpResponse('detection1')

使用postman访问其中一个接口:

[07/Oct/2024 19:16:31] "POST /twoshop/csrf_token/ HTTP/1.1" 403 2855

返回403代表是的请求头中csrf字段和cookie中csrf没有成功.(这里失败是我们在cookie和请求头中都没有设置csrf字段)

此时当我们访问这个项目中别的接口时发现都会报403错误.因为我们全局启用了django.middleware.csrf.CsrfViewMiddleware这个中间件,如果我们不想CsrfViewMiddleware进行全局csrf保护,只是想对某些视图不保护以及对某些视图进行保护,可以进行如下配置:

#引入装饰器,会覆盖全局中间件CsrfViewMiddleware
from django.views.decorators.csrf import csrf_protect,csrf_exempt


#会受到csrf保护
@csrf_protect
def csrf_detection(request):
      return HttpResponse('detection')


#不会受到csrf保护
@csrf_exempt
def csrf_detection1(request):
    return HttpResponse('detection1')

但是此时没加装饰器的视图,仍会受全局CsrfViewMiddleware影响,如果只想使用装饰器来确定是否需要保护,不需要全局保护,可以把全局CsrfViewMiddleware注释掉,只使用装饰器即可:

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
     #"django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

那我们该如何产生csrf呢?如何把它放入到cookie中并且把它放到请求头中呢?在这里我们不用考虑他们如何比较,CsrfViewMiddleware会帮我们比较.

在django中,我们创建一个表单在其中使用

{% csrf_token %}

代码如下:

<html>
<head>

</head>
<body>
    <form method="post" action="http://localhost:8000/twoshop/media/" 
                         enctype="multipart/form-data">
        {% csrf_token %}
        <label for="file">点击上传文件</label>
        <input type="file" id="file" name="file">
    <input type="submit">
    </form>
</body>
</html>

当用户访问一个包含表单的页面时,Django会生成一个随机的CSRF令牌并将其存储在cookie中。然后,这个令牌会被自动添加到所有表单中的隐藏字段中。渲染这个html时可以使用render()函数并返回.现在让我们访问一下:

多次刷新查看dom元素和网络返回的cookie:

经过对比发现,为什么我们每次刷新cookie里面的csrf不变,而dom元素里面的csrf一直在变化.这是应为dom里面csrf是掩码形式,cookie里面是掩码之前的形式,但是最后在服务端django进行比较的时候,比较的是密钥不是完整的令牌,所以无论dom怎么变都不影响(笔者这里也不是很了解,有问题请指正.)不过为什么django不直接把dom里面的csrf变成和cookie里面一样的呢,反而一直让他进行变化?

这里django做了更深一层的考虑,防止 BREACH攻击(有兴趣可以了解,这里笔者不做过多介绍).

生成csrf的这个表单可以直接提交,但是如果不是表单呢,如果是普通的ajax请求呢.这里我们就要在请求里面设置特殊的请求头:

'X-CSRFToken'=csrf

这里的csrf可以是dom中csrf生成的掩码,也可以是cookie中crsf的值.这里推荐使用掩码方式,因为更安全.

cookie的csrf值方式:

1设置settings.py文件:

CSRF_USE_SESSIONS=False
CSRF_COOKIE_HTTPONLY=False

2编写ajax请求(确保前端不会出现跨域错误):

<script>

    function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            // 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;
}
    const csrftoken = getCookie('csrftoken');


    fetch('http://127.0.0.1:8000/twoshop/csrf_token/',{
        method: 'POST',
        headers: {'X-CSRFToken': csrftoken,
        }
    }).then((result)=>{
    console.log(result)
    }).catch(error=>{
        console.log(error)
    })

</script>

dom的csrf方式:

1.设置settings.py:

CSRF_USE_SESSIONS=True
CSRF_COOKIE_HTTPONLY=True

2.设置ajax请求(确保不会出现跨域错误,{% csrf_token %}的出现,告诉我们这个html必须由django返回):

<html>
<head>

</head>
<body>
    <form method="post" action="http://localhost:8000/twoshop/media/" enctype="multipart/form-data">
        {% csrf_token %}
        <label for="file">点击上传文件</label>
        <input type="file" id="file" name="file">
    <input type="submit">
    </form>
</body>
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
const request = new Request(
    'http://127.0.0.1:8000/twoshop/csrf_token/',
    {
        method: 'POST',
        headers: {'X-CSRFToken': csrftoken},
        mode: 'same-origin' // Do not send CSRF token to another domain.
    }
);
fetch(request).then(function(response) {
    // ...
    console.log(response)
});
</script>
</html>

注意django开启csrf后 HTTP GET、HEAD、OPTIONS 或 TRACE 的传入请求,不存在保护,打比方来说,在遵守http规范的情况下,get请求只是请求数据,不修改数据,所以django认为跨站伪造get请求为安全的伪造请求.所以不进行保护.(如有错误,敬请指正.)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值