一、CSRF是什么
跨站请求伪造(CSRF)与跨站请求脚本正好相反。跨站请求脚本的问题在于,客户端信任服务器端发送的数据。跨站请求伪造的问题在于,服务器信任来自客户端的数据。
二、无CSRF时存在的隐患
跨站请求伪造是指攻击者通过HTTP请求江数据传送到服务器,从而盗取回话的cookie。盗取回话cookie之后,攻击者不仅可以获取用户的信息,还可以修改该cookie关联的账户信息。
三、Form提交(CSRF)
csrf验证其实是对http请求中一段随机字符串的验证,
如果没有这样一段随机字符串做验证,我们只要在另一个站点,写一个表单,提交到这个地址下,是一样可以发送数据的,这样就造成了极大的安全隐患。而我们新添加的csrf_token就是在我们自己的站点中,设置的隐藏参数,用来进行csrf验证。
四、Ajax提交 (CSRF)
我们可以从cookie中获取到这个随机字符串,要在引入jquery文件之后引入:
<script src="{% static 'js/jquery.cookie.js' %}"></script>
var csrftoken = $.cookie('csrftoken');
# 或通过标签取到
var csrftoken = $('input[name="csrfmiddlewaretoken"]').val();
这样变量csrftoken取到的就是我们的随机字符串;但是如果后台想要去接收这个随机字符串,也应该需要一个key,那这个key是什么?我们可以通过查找配置文件,通过控制台输出的方式验证这个key:
from django.conf import settings
print(settings.CSRF_HEADER_NAME)
最后输出的是:
HTTP_X_CSRFTOKEN
但这里需要注意的是,HTTP_X_CSRFTOKEN并不是请求头中发送给django真正拿到的字段名,前端发过去真正的字段名是:
X-CSRFtoken
将字段放入headers:
headers:{"X-CSRFToken":csrftoken},
放入data:
data:{"user":user,"csrfmiddlewaretoken":csrftoken}
那为什么从Django的控制台输出会得到HTTP_X_CSRFTOKEN呢?其实我们前端的请求头X-CSRFtoken发送到后台之后,django会做一个名字处理,在原来的字段名前家一个HTTP_,并且将原来的小写字符变成大写的,“-”会处理成下划线“_”,所以会有这两个字段的不一样。但本质上他们指向的都是同一个字符串。知道这一点之后,那么我们前端就可以真正地发起含有CSRF请求头的数据请求了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login/" method="post">
{% csrf_token %}
<input type="text" name="user" />
<input type="text" name="pwd" />
<input type="checkbox" name="rmb" value="1" /> 10s免登录
<input type="submit" value="提交" />
<input id="btn" type="button" value="按钮">
</form>
<script src="/static/jquery-1.12.4.js"></script>
<script src="/static/jquery.cookie.js"></script>
<script>
var csrftoken = $.cookie('csrftoken');
$(function () {
$('#btn').click(function () {
$.ajax({
url:'/login/',
type:"POST",
data:{'username':'root','pwd':'123123'},
header:{'X-CSRFToken':csrftoken},
success:function (arg) {
}
})
})
})
</script>
</body>
</html>
在页面中点击按钮之后,会发现请求成功!
那么这个时候有人会问,难道所有的ajax请求,都需要这样获取一次写进去吗,会不会很麻烦。针对这一点,jquery的ajax请求中为我们封装了一个方法:ajaxSetup,它可以为我们所有的ajax请求做一个集体配置,所以我们可以进行如下改造,这样不管你的ajax请求有多少,都可以很方便地进行csrf验证了:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login/" method="POST">
{% csrf_token %}
<input type="text" name="user" />
<input type="text" name="pwd" />
<input type="checkbox" name="rmb" value="1" /> 10s免登录
<input type="submit" value="提交" />
<input id="btn" type="button" value="按钮">
</form>
<script src="/static/jquery-1.12.4.js"></script>
<script src="/static/jquery.cookie.js"></script>
<script>
var csrftoken = $.cookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
$(function () {
$('#btn').click(function () {
$.ajax({
url:'/login/',
type:"POST",
data:{'username':'root','pwd':'123123'},
success:function (arg) {
}
})
})
});
</script>
</body>
</html>
五、csrf装饰器配置
在平时场景中,并不一定所有的接口验证都需要进行csrf验证,我们采用的是在settings.py中间件配置进行全局配置,如果遇到不需要验证的,我们可以采用局部禁用。
FBV
from django.views.decorators.csrf import csrf_exempt,csrf_protect
@csrf_protect #局部使用
def index(request):
.....
@csrf_exempt #局部禁用
def login(request):
.....
CBV
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.shortcuts import render, HttpResponse
from django.views import View
class Cs(View):
#@method_decorator(csrf_exempt)
@method_decorator(csrf_protect)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
return HttpResponse('GET,响应内容')
def post(self, request, *args, **kwargs):
return HttpResponse('Post,响应内容')
或
六、Django加装饰器
from django.utils.decorators import method_decorator
# 1.方法
@csrf_protect
def get(request):
pass
# 2.类上
@method_decorator(wraper,name="dispatch")
class Foo(View):
# @method_decorator(csrf_exempt)
# @method_decorator(csrf_protect)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self,request):
pass
def post(self,request):
pass
注意:csrf必须加在dispatch上
七、手动将csrftoken写入cookie的方法
-
手动设置,在view 中添加(建议采用2,3,4种方法)
request.META["CSRF_COOKIE_USED"] = True
-
手动调用 csrf 中的 get_token(request) 或 rotate_token(request) 方法。
from django.middleware.csrf import get_token ,rotate_token def server(request): # get_token(request) // 两者选一 # rotate_token(request) // 此方法每次设置新的cookies return render(request, ‘server.html‘)
-
在HTML模板中添加 {% csrf_token %}
-
在需要设置cookie的视图上加装饰器 ensure_csrf_cookie()
from django.views.decorators.csrf import ensure_csrf_cookie @ensure_csrf_cookie def server(request): return render(request,'server.html')