1. 程式人生 > >django的crsf機制防禦詳解及在前後端分離中post資料到django-vue js 使用session 、cookie、angular cookie儲存token

django的crsf機制防禦詳解及在前後端分離中post資料到django-vue js 使用session 、cookie、angular cookie儲存token

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中。

目錄

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中。