django的crsf机制防御详解及在前后端分离中post数据到django-vue

django的crsf机制防御详解及在前后端分离中post数据到django

CSRF(Cross Site Request Forgery) 跨站点伪造请求

某个用户已经登陆了你的网站,另外有一个恶意的网站有一个指向你网站的链接,那么当用户点击这个链接时,就会请求你的网站,但是你的网站以为是用户发来的请求,这时恶意网站就得逞了。

django的应对措施

用户在post请求时,发送给用户一个token,然后在django内部实现了一个校验这个token的函数,当正确时,服务器就会返回正确的内容,如果不正确就是返回403。

django自带的表单模版中可以直接使{% csrf_token %}来通过django的检验。

如何工作

引用django官网 点此查看

CSRF保护基于以下内容:

  1. CSRF cookie,基于随机密钥值,其他站点无权访问。

这个cookie设置为CsrfViewMiddleware。django.middleware.csrf.get_token()如果尚未在请求中设置,则与每个已调用的响应(内部用于检索CSRF令牌的函数)一起发送。
为了防止BREACH攻击,令牌不仅仅是秘密; 随机盐被置于秘密之前并用于加扰它。
出于安全原因,每次用户登录时都会更改密钥的值。

  1. 所有传出POST表单中都有一个名为“csrfmiddlewaretoken”的隐藏表单字段。此字段的值同样是秘密的值,盐添加到它并用于加扰它。每次调用时都会重新生成salt,get_token()以便在每个此类响应中更改表单字段值。
    这部分由模板标签完成。

  2. 对于未使用HTTP GET,HEAD,OPTIONS或TRACE的所有传入请求,必须存在CSRF cookie,并且’csrfmiddlewaretoken’字段必须存在且正确。如果不是,用户将收到403错误。
    验证’csrfmiddlewaretoken’字段值时,只将秘密而不是完整令牌与cookie值中的秘密进行比较。这允许使用不断变化的令牌。虽然每个请求都可以使用自己的令牌,但秘密仍然是所有人共同的。
    这项检查是通过CsrfViewMiddleware。

  3. 此外,对于HTTPS请求,严格的引用检查由 CsrfViewMiddleware。这意味着即使子域可以在您的域上设置或修改cookie,它也不能强制用户发布到您的应用程序,因为该请求不会来自您自己的确切域。
    这也解决了在使用会话独立秘密时在HTTPS下可能发生的中间人攻击,因为Set-Cookie即使用户在HTTPS下与某个站点通信时,HTTP 头(不幸)也被接受了。(对HTTP请求不进行引用检查,因为在HTTP下,Referer标头的存在不够可靠。)
    如果CSRF_COOKIE_DOMAIN设置了该设置,则将引用者与其进行比较。此设置支持子域。例如, 将允许来自和的POST请求 。如果未设置该设置,则引用必须与HTTP 标头匹配。CSRF_COOKIE_DOMAIN = ‘.example.com’www.example.comapi.example.comHost
    可以使用该CSRF_TRUSTED_ORIGINS设置将已接受的引用扩展到当前主机或cookie域之外。

请求分析
在django第一次渲染渲染模版的时候,{% csrf_token %}

会调用django.middleware.csrf.get_token生成一个64位的无序字符csrfmiddlewaretoken放在html里,并且会在浏览器的cookie里面设置64位的csrftoken=,然后在用户点击提交按钮的时候会在后台通过调用某种算法来计算这两个值生成的token是否相等来方法csrf攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="container">
<form class="form-signin" method="POST">
{% csrf_token %}
<h2 class="form-signin-heading">Please login in</h2>
<label for="inputEmail" class="sr-only">Email address</label>
<input name="loginEmail" type="email" id="inputEmail" class="form-control" placeholder="Email address" value = "{{myLogin.loginEmail}}" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input name="loginPassword" type="password" id="inputPassword" class="form-control" placeholder="Password" required>
<!-- <div class="checkbox">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div> -->
<button class="btn btn-lg btn-primary btn-block" type="submit">Login in</button>
</form>
</div> <!-- /container -->

 

下面来是生成token的算法分析
django source

主要的一些算法代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_token(request):
"""
Return the CSRF token required for a POST form. The token is an
alphanumeric value. A new token is created if one is not already set.
A side effect of calling this function is to make the csrf_protect
decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie'
header to the outgoing response. For this reason, you may need to use this
function lazily, as is done by the csrf context processor.
"""
if "CSRF_COOKIE" not in request.META:
csrf_secret = _get_new_csrf_string()
request.META["CSRF_COOKIE"] = _salt_cipher_secret(csrf_secret)
else:
csrf_secret = _unsalt_cipher_token(request.META["CSRF_COOKIE"])
request.META["CSRF_COOKIE_USED"] = True
return _salt_cipher_secret(csrf_secret)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from django.utils.crypto import get_random_string
import string

CHAR = string.ascii_lowercase
SECRET_LEHGTH = 3


def get_new_sting():
return get_random_string(SECRET_LEHGTH, allowed_chars=CHAR)

def salt_cipher_secret(secret):
# 盐的长度是固定的
salt = get_new_sting()
pairs = zip((CHAR.index(x) for x in secret), (CHAR.index(x) for x in salt))
cipher = ''.join(CHAR[(x+y) % len(CHAR)] for x, y in pairs)
return salt + cipher
token = salt_cipher_secret(get_new_sting())
# print(salt_cipher_secret('abc'))

def unsalt_cipher_secret(token):
salt = token[:SECRET_LEHGTH]
token = token[SECRET_LEHGTH:]
pairs = zip((CHAR.index(x) for x in salt), (CHAR.index(x) for x in token))
secret = ''.join(CHAR[y-x] for x, y in pairs)
return secret
unsalt_cipher_secret(token)

cookie中的值csrftokrn在用户请求了一次后以后不会再改变,而csrfmiddlewaretoken会每次改变

主要要用的三个函数的作用

  1. get_token是返回csrfmiddlewaretoken的函数,同时第一次请求的话会设置csrftoken到到ccookie中 ,要是重复请求则不会再重新生成。
  2. salt_cipher_secret是生成csrfmiddlewaretokencsrftoken的主要函数,此函数需要一个secret,通过这个secret然后加上salt就可以用相同的secret生成不同的64位token。
  3. unsalt_cipher_secret是通过token来反向生成secret的函数,此方法主要用两个用途:
    • 重复请求时,通过cookie中的csrftoken然反向生成生成这个值的secret,然后使用这个secret来生成表单中的csrfmiddlewaretoken,
    • 提交表单后通过这个函数, post数据中的csrfmiddlewaretoken和cookie中的csrftoken会通过这个函数计算出生成这两个值的secret如果相等,就验证成功,如果不想等就会返回403

算法分析
salt_cipher_secret
生成需要加入的盐salt,然后分别计算出secret和salt在CHAR中的索引值,然后将对应的索引值相加后除以CHAR的长度后得到的余数,再在CHAR找出对应的值,然后再与salt相加返回,
unsalt_cipher_secret
此函数的通过token反向得到secret与salt_cipher_secret步骤相反,通过切片得到salt和token1,然后分别找出在CHAR中的索引值,然后相减后再在CHAR中找出对应索引上的值,然后拼接成secret。

前后端分离后前端验证django的token

只要前端在post数据的时候只要把每次通过get_token函数生成的随机字段发送过去就可以验证了,可以放在post数据中,也可以放在请求头中,

放在请求头中

django的views中设置一个可以获取csrfmiddlewaretoken的方法就是调用get_token函数返回一个字串

django

1
2
3
4
5
6
# views.py
from django.middleware.csrf import get_token

def token(request):
token = get_token(request=request)
return JsonResponse({'token': token}, json_dumps_params={'indent':3})

 

前端vue
先请求这个返回token的接口,然后将返回的值加入到post放的请求,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sumbit() {
this.$axios.get("/api/get_token").then(response => {
this.cookie_data = response.data['token'];
this.post(this.cookie_data)
});
},
post(CSRFToken) {
this.dialog_operate = false;
console.log(this.data)
this.$axios
.post("/api/operate",
this.data,
{
headers: {'X-CSRFToken': CSRFToken,'Content-Type':'application/json'}
}

)

 

 

 

 

 

js 使用session 、cookie、angular cookie保存token

 

目录

1、前言

2、js前端使用session保存token

3、js前端使用cookie保存token

4、js前端使用angular cookie保存token

 

内容

1、前言

  在前端请求后台的API接口的时候,为了安全性,一般需要在用户登录成功之后才能发送其他请求。

  所以,在用户登录成功之后,后台会返回一个token给前端,这个时候我们就需要把token暂时保存在本地,每次发送请求的时候需要在header里面带上token,这时候本地的token和后台数据库中的token进行一个验证,如果两者一致,则请求成功,否则,请求失败。

  如下图,登录成功之后返回token:

  

  如下图所示,进行其他请求的时候在header里面带上token:

  

 

 2、js前端使用session保存token

  sessionStorage属性允许你访问一个Session Storage对象,它与localStorage相似,不同之处在于localStorage里面存储的数据没有过期设置,而存储在sessionStorage里面的数据在页面会话结束时会被清除。页面会话在浏览器打开期间一直保持,并且重新加载或者恢复页面仍会保持原来的页面会话。

  下面是session中储存token的具体的语法:

复制代码
// 保存数据到sessionStorage
sessionStorage.setItem('key', 'value');

// 从sessionStorage获取数据
var data = sessionStorage.getItem('key');

// 从sessionStorage删除保存的数据
sessionStorage.removeItem('key');

// 从sessionStorage删除所有保存的数据
sessionStorage.clear();
复制代码

3、js前端使用cookie保存token

  将token保存在cookie中,一旦浏览器关闭,cookie中的token就会被清空。

  下面是cookie中存储token的语法:

//将token保存在cookie中
document.cookie=token;

//从cookie中取出token
var token = document.cookie.split(";")[0];

4、js前端使用angular cookie保存token

  在angular中引入cookie必须首先引入angular-cookies.js文件。下面是直接能够在浏览器中打开的版本的链接:https://cdn.bootcss.com/angular-cookie/4.1.0/angular-cookie.js

  下面是angular cookie中存储token的语法:

复制代码
//存储token
angular
    .module('theme.demos.login_page', ['ngCookies'])
    .controller('LoginController'['$scope','$cookieStore',function($scope,$cookieStore) {
         var valueIndex=encodeURI(data.data.token);            
         $cookieStore.put('tokenIndex',valueIndex);
})

//拿到token    
angular
    .module('theme.demos.login_page', ['ngCookies'])
    .controller('LoginController'['$scope','$cookieStore',function($scope,$cookieStore) {
         var token= $cookieStore.get('tokenIndex');
})
复制代码

  以上就是三种较为常见的js中存储token的方法。并且这三种方法在Google浏览器、Firefox浏览器、IE浏览器中均可兼容,但是在Safari浏览器中会出现不兼容问题(Apple),主要是无法将token写入cookie中。

posted @ 2018-12-01 22:26  Alive_2020  阅读(1832)  评论(1编辑  收藏  举报