csrf攻击流程
csrftoken机制
<form action="/login/" method="post"> {% csrf_token %} -- 会生成一个隐藏的input标签,value属性里面放着token值,name属性值为csrfmiddlewaretoken 用户名: <input type="text" name="username"> <input type="submit"> </form> {% csrf_token %} 当我们使用form表单标签来发送请求时,如果需要通过csrftoken认证,那么必须将它写到我们的form表单标签里面,里面的任意位置
校验过程解释
html页面中的{% csrf_token %} <input type="hidden" name="csrfmiddlewaretoken" value="WVHKQeAuMS4RGqyLybryIBAfacDa1Dp7PEaB3Badv3y0fvLqydX36xAVen6z3oS4"> cookie中的 csrftoken:CeFG6SA8Y8hcHAX5R93sxrS37v3iFFlcvX8xjfaRHjLlgFaKRbzXVnSJbGwHHqO9 django会取出提交数据部分的token值和cookie中的token值进行如下比较: WVHKQeAuMS4RGqyLybryIBAfacDa1Dp7PEaB3Badv3y0fvLqydX36xAVen6z3oS4 -- 前32位可以对后面的几位进行解密,解密出以secret_key1 一个字符串 CeFG6SA8Y8hcHAX5R93sxrS37v3iFFlcvX8xjfaRHjLlgFaKRbzXVnSJbGwHHqO9 -- 前32位可以对后面的几位进行解密,解密出以secret_key2 一个字符串 secret_key1 = secret_key2 说明: token字符串的前32位是salt, 后面是加密后的token, 通过salt能解密出唯一的secret。 django会验证表单中的token和cookie中token是否能解出同样的secret,secret一样则本次请求合法。 MIDDLEWARE = [ ... 'django.middleware.csrf.CsrfViewMiddleware', ... ] 源码 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
ajax请求通过csrftoken认证
方式1
首先在html文件中间,写上我们的{% csrf_token %}
$('#btn').click(function () {
var uname = $("#uname").val();
// 获取csrfmiddlewaretoken的input标签value属性对应的值
var token = $('[name="csrfmiddlewaretoken"]').val();
$.ajax({
url:'/login/',
type:'post',
//将token值放到请求数据部分
data:{uname:uname,csrfmiddlewaretoken:token},
success:function (res) {
console.log(res);
}
})
})
方式2:
使用'{{ csrf_token }}' 这个模板渲染标签,这样就不要在我们html页面中写{% csrf_token %}了。
$('#btn').click(function () {
//方式1
var uname = $("#uname").val();
// 直接就能得到csrfmiddlewaretoken的input标签value属性对应的值
var token = '{{ csrf_token }}';
$.ajax({
url:'/login/',
type:'post',
data:{uname:uname,csrfmiddlewaretoken:token},
success:function (res) {
console.log(res);
}
})
})
方式3
借助js操作cookie的方法来获取到cookie中的csrftoken那个键对应的值
然后将这个值组成一个请求头,放到此次请求中
headers:{"X-CSRFToken":这个值}
这个值,通过js能够获取,通过jquery也能获取,我们看一下jquery如何操作cookie
jquery操作cookie参考:https://www.cnblogs.com/clschao/articles/10480029.html
下载网址:http://plugins.jquery.com/cookie/
$('#btn').click(function () {
//方式3
// 前提:需要在html页面中写上{% csrf_token %}或者{{ csrf_token }},不然此次获取这个html页面的时候,响应中不会有这个csrftoken的cookie值
var uname = $("#uname").val();
// 通过js或者jquery来获取cookie中的csrftoken这个键对应的token值
var token = $.cookie('csrftoken');
$.ajax({
url:'/login/',
type:'post',
// 将获取到的token值放到请求头中,这个请求头键值对的的键必须是"X-CSRFToken"
headers:{
"X-CSRFToken":token,
},
// django先去获取请求数据部分的token值,获取不到,就去找一个叫做X-CSRFToken请求头键值对,他的值和cookie中的csrftoken的值要相等。
data:{uname:uname,},
success:function (res) {
console.log(res);
}
})
})
html部分 用户名: <input type="text" name="username"> 密码: <input type="text" name="password"> 头像: <input type="file" name="touxiang" > <button id="sub">上传</button> js部分 $('#sub').click(function () { //ajax上传文件必须依赖于FormData对象 var formdata = new FormData(); var uname = $('[name="username"]').val(); var pwd = $('[name="password"]').val(); // 获取浏览器上的文件数据方法:$('[name="touxiang"]')[0].files[0] var file_obj = $('[name="touxiang"]')[0].files[0]; {#formdata.append('xx','oo') //django 获取数据:post request.POST.get('xx') -- oo#} //request.POST formdata.append('uname',uname) formdata.append('pwd',pwd) {# {% csrf_token %}#} formdata.append('csrfmiddlewaretoken','{{ csrf_token }}') //request.FILES formdata.append('touxiang',file_obj) $.ajax({ {#url:'/upload/',#} url:'', //如果路径为空,那么使用当前页面的路径,也就是往当前页面的路径下进行提交 type:'post', data:formdata, // 下面的两个参数的意思是,不要对数据进行任何的预处理和加工 // 固定写法 processData:false, contentType:false, success:function (res) { console.log(res); } }) })
views页面
def login(request): if request.method == "GET": return render(request,"login.html") else: # file_obj = request.FILES.get('touxiang') # with open(file_obj.name,"wb") as fp: # for line in file_obj: # fp.write(line) # 方式二: chunks()可以设定传输文件时每次传送的大小 file_obj = request.FILES.get('touxiang') with open(file_obj.name,"wb") as fp: for chunk in file_obj.chunks(): fp.write(chunk) return HttpResponse('OK')
上传多文件
标签写法: <input type="file" name="touxiang" multiple>