5 - django-csrf-session&cookie

1 CSRF跨站請求僞造

        CSRF跨站點請求僞造(Cross—Site Request Forgery),跟XSS攻擊同樣,存在巨大的危害性,你能夠這樣來理解:攻擊者盜用了你的身份,以你的名義發送惡意請求,對服務器來講這個請求是徹底合法的,可是卻完成了攻擊者所指望的一個操做,好比以你的名義發送郵件、發消息,盜取你的帳號,添加系統管理員,甚至於購買商品、虛擬貨幣轉帳等。html

1.1 CSRF攻擊介紹及防護

人設:Web A爲存在CSRF漏洞的網站,Web B爲攻擊者構建的惡意網站,User C爲Web A網站的合法用戶。
CSRF攻擊攻擊原理及過程以下:前端

  1. 用戶C打開瀏覽器,訪問受信任網站A,輸入用戶名和密碼請求登陸網站A;
  2. 在用戶信息經過驗證後,網站A產生Cookie信息並返回給瀏覽器,此時用戶登陸網站A成功,能夠正常發送請求到網站A;
  3. 用戶未退出網站A以前,在同一瀏覽器中,打開一個TAB頁訪問網站B;
  4. 網站B接收到用戶請求後,返回一些攻擊性代碼,併發出一個請求要求訪問第三方站點A;
  5. 瀏覽器在接收到這些攻擊性代碼後,根據網站B的請求,在用戶不知情的狀況下攜帶Cookie信息,向網站A發出請求。網站A並不知道該請求實際上是由B發起的,因此會根據用戶C的Cookie信息以C的權限處理該請求,致使來自網站B的惡意代碼被執行。

PS:還有一種狀況是:多窗口瀏覽器,它便捷的同時也帶來了一些問題,由於多窗口瀏覽器新開的窗口是具備當前全部會話的。即我用IE登錄了個人Blog,而後我想看新聞了,又運行一個IE進程,這個時候兩個IE窗口的會話是彼此獨立的,從看新聞的IE發送請求到Blog不會有我登陸的cookie;可是多窗口瀏覽器永遠都只有一個進程,各窗口的會話是通用的,即看新聞的窗口發請求到Blog是會帶上我在blog登陸的cookie,也有可能產生CSRF攻擊。java

下面是一個實例:python

1. 受害者 Bob 在銀行有一筆存款,經過對銀行的網站發送請求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可使 Bob 把 1000000 的存款轉到 bob2 的帳號下。一般狀況下,該請求發送到網站後,服務器會先驗證該請求是否來自一個合法的 session,而且該 session 的用戶 Bob 已經成功登錄。

2. 黑客 Mallory 本身在該銀行也有帳戶,他知道上文中的 URL 能夠把錢進行轉賬操做。Mallory 能夠本身發送一個請求給銀行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。可是這個請求來自 Mallory 而非 Bob,他不能經過安全認證,所以該請求不會起做用。

3. 這時,Mallory 想到使用 CSRF 的攻擊方式,他先本身作一個網站,在網站中放入以下代碼: src=」http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory 」,而且經過廣告等誘使 Bob 來訪問他的網站。當 Bob 訪問該網站時,上述 url 就會從 Bob 的瀏覽器發向銀行,而這個請求會附帶 Bob 瀏覽器中的 cookie 一塊兒發向銀行服務器。大多數狀況下,該請求會失敗,由於他要求 Bob 的認證信息。可是,若是 Bob 當時恰巧剛訪問他的銀行後不久,他的瀏覽器與銀行網站之間的 session 還沒有過時,瀏覽器的 cookie 之中含有 Bob 的認證信息。這時,悲劇發生了,這個 url 請求就會獲得響應,錢將從 Bob 的帳號轉移到 Mallory 的帳號,而 Bob 當時絕不知情。等之後 Bob 發現帳戶錢少了,即便他去銀行查詢日誌,他也只能發現確實有一個來自於他本人的合法請求轉移了資金,沒有任何被攻擊的痕跡。而 Mallory 則能夠拿到錢後逍遙法外。

1.2 防護CSRF攻擊

        目前防護 CSRF 攻擊主要有三種策略:驗證 HTTP Referer 字段;在請求地址中添加 token 並驗證;在 HTTP 頭中自定義屬性並驗證。jquery

1.2.1 驗證 HTTP Referer 字段

        根據 HTTP 協議,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址。在一般狀況下,訪問一個安全受限頁面的請求來自於同一個網站,好比須要訪問 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用戶必須先登錄 bank.example,而後經過點擊頁面上的按鈕來觸發轉帳事件。這時,該轉賬請求的 Referer 值就會是轉帳按鈕所在的頁面的 URL,一般是以 bank.example 域名開頭的地址。而若是黑客要對銀行網站實施 CSRF 攻擊,他只能在他本身的網站構造請求,當用戶經過黑客的網站發送請求到銀行時,該請求的 Referer 是指向黑客本身的網站。所以,要防護 CSRF 攻擊,銀行網站只須要對於每個轉帳請求驗證其 Referer 值,若是是以 bank.example 開頭的域名,則說明該請求是來自銀行網站本身的請求,是合法的。若是 Referer 是其餘網站的話,則有多是黑客的 CSRF 攻擊,拒絕該請求。nginx

        這種方法的顯而易見的好處就是簡單易行,網站的普通開發人員不須要操心 CSRF 的漏洞,只須要在最後給全部安全敏感的請求統一增長一個攔截器來檢查 Referer 的值就能夠。特別是對於當前現有的系統,不須要改變當前系統的任何已有代碼和邏輯,沒有風險,很是便捷。程序員

        然而,這種方法並不是萬無一失。Referer 的值是由瀏覽器提供的,雖然 HTTP 協議上有明確的要求,可是每一個瀏覽器對於 Referer 的具體實現可能有差異,並不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴於第三方(即瀏覽器)來保障,從理論上來說,這樣並不安全。事實上,對於某些瀏覽器,好比 IE6 或 FF2,目前已經有一些方法能夠篡改 Referer 值。若是 bank.example 網站支持 IE6 瀏覽器,黑客徹底能夠把用戶瀏覽器的 Referer 值設爲以 bank.example 域名開頭的地址,這樣就能夠經過驗證,從而進行 CSRF 攻擊。ajax

        即使是使用最新的瀏覽器,黑客沒法篡改 Referer 值,這種方法仍然有問題。由於 Referer 值會記錄下用戶的訪問來源,有些用戶認爲這樣會侵犯到他們本身的隱私權,特別是有些組織擔憂 Referer 值會把組織內網中的某些信息泄露到外網中。所以,用戶本身能夠設置瀏覽器使其在發送請求時再也不提供 Referer。當他們正常訪問銀行網站時,網站會由於請求沒有 Referer 值而認爲是 CSRF 攻擊,拒絕合法用戶的訪問。redis

1.2.2 在請求地址中添加 token 並驗證

        CSRF 攻擊之因此可以成功,是由於黑客能夠徹底僞造用戶的請求,該請求中全部的用戶驗證信息都是存在於 cookie 中,所以黑客能夠在不知道這些驗證信息的狀況下直接利用用戶本身的 cookie 來經過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能僞造的信息,而且該信息不存在於 cookie 之中。能夠在 HTTP 請求中以參數的形式加入一個隨機產生的 token,並在服務器端創建一個攔截器來驗證這個 token,若是請求中沒有 token 或者 token 內容不正確,則認爲多是 CSRF 攻擊而拒絕該請求。

        這種方法要比檢查 Referer 要安全一些,token 能夠在用戶登錄後產生並放於 session 之中,而後在每次請求時把 token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以參數的形式加入請求。對於 GET 請求,token 將附在請求地址以後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。 而對於 POST 請求來講,要在 form 的最後加上 ,這樣就把 token 以參數的形式加入請求了。可是,在一個網站中,能夠接受請求的地方很是多,要對於每個請求都加上 token 是很麻煩的,而且很容易漏掉,一般使用的方法就是在每次頁面加載時,使用 javascript 遍歷整個 dom 樹,對於 dom 中全部的 a 和 form 標籤後加入 token。這樣能夠解決大部分的請求,可是對於在頁面加載以後動態生成的 html 代碼,這種方法就沒有做用,還須要程序員在編碼時手動添加 token。

        該方法還有一個缺點是難以保證 token 自己的安全。特別是在一些論壇之類支持用戶本身發表內容的網站,黑客能夠在上面發佈本身我的網站的地址。因爲系統也會在這個地址後面加上 token,黑客能夠在本身的網站上獲得這個 token,並立刻就能夠發動 CSRF 攻擊。爲了不這一點,系統能夠在添加 token 的時候增長一個判斷,若是這個連接是鏈到本身本站的,就在後面添加 token,若是是通向外網則不加。不過,即便這個 csrftoken 不以參數的形式附加在請求之中,黑客的網站也一樣能夠經過 Referer 來獲得這個 token 值以發動 CSRF 攻擊。這也是一些用戶喜歡手動關閉瀏覽器 Referer 功能的緣由。

1.2.3 在 HTTP 頭中自定義屬性並驗證

        這種方法也是使用 token 並進行驗證,和上一種方法不一樣的是,這裏並非把 token 以參數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裏。經過 XMLHttpRequest 這個類,能夠一次性給全部該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,經過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔憂 token 會透過 Referer 泄露到其餘網站中去。
        然而這種方法的侷限性很是大。XMLHttpRequest 請求一般用於 Ajax 方法中對於頁面局部的異步刷新,並不是全部的請求都適合用這個類來發起,並且經過該類請求獲得的頁面不能被瀏覽器所記錄下,從而進行前進,後退,刷新,收藏等操做,給用戶帶來不便。另外,對於沒有進行 CSRF 防禦的遺留系統來講,要採用這種方法來進行防禦,要把全部請求都改成 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。

1.2.4 django csrf token

        django中,是經過中間件來對CSRF進行驗證的,有關中間件的信息,將在後面的小節中說明。PS:還記得以前在settings.py中註釋掉的 django.middleware.csrf.CsrfViewMiddleware 嗎?,它就是用於檢測CSRF的中間件。若是咱們render返回頁面時,那麼用戶使用get請求訪問網站時,站點會返回一段隨機的csrf_token,同時也會在cookie中寫一個名爲csrftoken的key,這兩個key對應的值是不一樣的。

        根據這兩種token,咱們有兩種方式,傳遞、驗證CSRF token(首先打開csrf中間件,若是以前註釋掉的話)即:一、form表單提交 二、ajax提交

1.3 form表單提交

        在表單中添加{% csrf_token %} 來渲染服務端返回的csrf_token,它會以hidden的形式在form中產生一個input標籤,在咱們利用form提交時會一併攜帶發送給服務端進行驗證,當咱們使用ajax代替form的submit進行提交時,能夠在data字段,來獲取form表單中渲染的csrf_token,進行提交,固然,這裏的key的名稱必須爲csrfmiddlewaretoken,不然沒法識別,從生成的input的name屬性就能看到。

<form action="/login" method="post" >
{% csrf_token %}        #渲染csrf_token
    <div>
        <label for="user">用戶名:</label>
        <input type="text" id="user" name="user" placeholder='Username'>
    </div>
    <div>
        <label for="passwd">密碼:</label>
        <input type="password" id="passwd" name="passwd" placeholder='Password'>
    </div>
        <input type="submit" value="登錄">
</form>

在前端咱們看到的是:

<form action="/file" method="post" enctype="multipart/form-data">
<input type="hidden" name="csrfmiddlewaretoken" value="8HElv24C9ajJZUE8gX3Cs9e0iIaXgX9iMvIfXZsKthjazqwnAvtrEvS84QC2pEik">   # 渲染的input標籤
    <div>
        <label for="user">用戶名:</label>
        <input type="text" id="user" name="user" placeholder='Username'>
    </div>
    <div>
        <label for="passwd">密碼:</label>
        <input type="password" id="passwd" name="passwd" placeholder='Password'>
    </div>
        <input type="submit" value="登錄">
</form>

經過AJAX來獲取Form的csrftoken進行提交 

$(function(){
    $('#btn').click(function () {
        $.ajax({
            url:'/login',
            type:'POST',
            data:{'csrfmiddlewaretoken':$("input[name='csrfmiddlewaretoken']").val(),......},    // name屬性選擇器獲取csrftoken
            success:function () {
                alert(123)
        }
        })
    })
})

爲何是'csrfmiddlewaretoken'這個名字?

# 一、倒入 csrf中間件查看源碼,獲取的究竟是什麼信息
from django.middleware.csrf import CsrfViewMiddleware
 
# 二、源碼中關於from的csrf定義 295行
request_csrf_token = ""
if request.method == "POST":
    try:
        request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')   # 這裏獲取了csrfmiddlewaretoken,因此咱們form必需要傳遞這個key才行。

1.4 ajax提交  

        當頁面中沒有form表單(或者說不想使用form手動渲染),那麼還可使用cookie中的csrftoken進行csrf驗證,因爲cookie在請求時是放在請求頭中的,因此這裏使用ajax的headers來定製請求頭,注意請求頭的key必須爲(X-CSRFtoken),利用jquery cookie插件,使用$.cookie('csrftoken')來獲取對應的值便可。

$(function(){
    $('#btn').click(function () {
        $.ajax({
            url:'/index',
            type:'post',
            headers:{'X-CSRFtoken':$.cookie('csrftoken')},     // 構建請求頭
            data:{'user':$('#user').val(),'passwd':$('#passwd').val()},
            success:function () {
                alert(123)
        }
        })
    })
})

爲何是'X-CSRFtoken'這個名字?

# 一、倒入 csrf中間件查看源碼,獲取的究竟是什麼信息
from django.middleware.csrf import CsrfViewMiddleware
 
 
# 二、源碼中關於header中的csrf說明,295行(django 1.11)
request_csrf_token = ""
if request.method == "POST":
    try:
        request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
    except IOError:
        pass
if request_csrf_token == "":
    request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')
# 若是用戶提交的數據中沒有request_csrf_token,那麼嘗試從meta中獲取數據(包含用戶提交的全部數據也包括請求頭,從中獲取settings.CSRF_HEADER_NAME的值用做request_csrf_token)
 
     
# 三、倒入settings查找csrftoken的真正名稱
from django.conf import settings
print(settings.CSRF_HEADER_NAME)     # 結果爲:HTTP_X_CSRFTOKEN
 
 
# 四、當咱們利用ajax定製 request headers時,django會爲咱們在咱們定製的key前面添加HTTP_來進行標識,
# 因此咱們實際上傳遞的key應該爲X_CSRFTOKEN,
# 又由於提交數據時不能提交包含下劃線的key,會被認爲是非法數據,因此,這裏的名稱爲X-CSRFTOKEN
# 官方建議爲:X-CSRFtoken

PS:這裏的settings模塊和settings.py文件不是一回事兒,settings模塊中包含了django不少的默認配置信息,若是咱們要對某個配置信息進行修改,能夠在settings.py中定義,若是沒有定義,那麼django就會按照settings中的默認配置進行處理。

擴展信息:

在商城 APP 開發時,在與客戶端聯調 API 接口的過程當中,咱們發現,在 PHP 的 $_SERVER 超全局變量中某些自定義的 HEADER 字段竟然獲取不到。經過抓包工具查看數據包,該自定義頭的確是存在的。
後來經過調試咱們發現,根本緣由是客戶端錯誤地將字段名中的中劃線寫成了下劃線。例如,應該是 X-ACCESS-TOKEN,卻被寫成了 X_ACCESS_TOKEN。
問題自己很好解決。然而咱們想要知道,服務器爲什麼要對字段名中使用了下劃線的頭視而不見呢?而且,無論是 Apache 仍是 Nginx,對於這樣的狀況,都不約而同地採起了同樣的策略。
在 RFC 2616 4.2 節中,有以下一段話:
Request (section 5) and Response (section 6) messages use the generic message format of RFC 822 [9] for transferring entities (the payload of the message).
這段話的意思,就是說 HTTP/1.1 的請求和響應消息使用 RFC 822 中的通用消息格式來傳輸實體(消息載荷)。
在 RFC 822 3.1.2 節中,對於消息格式的說明,有這樣一句話:
The field-name must be composed of printable ASCII characters (i.e., characters that have values between 33. and 126., decimal, except colon).
也就是說,HEADER 字段名能夠可由可打印的 ASCII 字符組成(也就是十進制值在 33 和 126 之間的字符,不含冒號)。
不含冒號很容易理解,由於 Field-Name 和 Value 之間須要用冒號分割。然而,咱們經過查詢 ASCII 碼錶可知,下劃線的十進制 ASCII 值爲 95,也在此範圍以內!
其實,在 HEADER 字段名中使用下劃線實際上是合法的、符合 HTTP 標準的。服務器之因此要默認禁止使用是由於 CGI 歷史遺留問題。下劃線和中劃線都爲會被映射爲 CGI 系統變量中名中的下劃線,這樣容易引發混淆。
在 nginx 服務器中,經過顯式地設置 underscores_in_headers on 能夠開啓在字段名中使用下劃線。默認該選項是關閉的,因此在默認狀況下,全部包含下劃線的字段名都會被丟棄。
在咱們的開發機中的確也開啓了這個選項,爲啥仍是不能拿到字段名中包含下劃線的 HEADER 呢?這是由於咱們訪問這臺開發機的時候,前面還有一層代理服務器,而這臺代理服務器沒有開啓相關選項,致使這種 HEADER 被代理服務器直接丟棄。所以也強烈建議不要在 HEADER 的 Field-Name 中使用下劃線。

  當咱們對以前的ajax提交進行改造時,因爲以前沒有csrftoken的驗證,那麼若是咱們開啓驗證,那麼就須要對全部的ajax提交進行修改,那麼這裏可使用ajax的setup進行全局的配置(能夠查看上一篇ajax部份內容)。

$(function(){
 
    // 利用ajaxSetup進行全局配置
    $.ajaxSetup({
        beforeSend:function (xhr,settings) {                            // 在發送ajax請求以前,要執行的函數
            xhr.setRequestHeader('X-CSRFtoken',$.cookie('csrftoken'))   // 定製請求頭,添加csrftoken
        }
    })
 
    // 其餘ajax請求,就無需再定製header發送csrf了。
    $('#btn').click(function () {
        $.ajax({
            url:'/index',
            type:'post',
            data:{'user':$('#user').val(),'passwd':$('#passwd').val()},
            success:function () {
                alert(123)
        }
        })
    })
})

PS:好比get,delete等請求是不須要攜帶csrftoken的,那麼咱們可使用在進行全局配置時,對HTTP請求方式進行判斷。

# 判斷HTTP請求方式。針對POST請求提交CSRFtoken
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    {% csrf_token %}
  
    <input type="button" onclick="Do();"  value="Do it"/>
  
    <script src="/static/plugin/jquery/jquery-1.8.0.js"></script>
    <script src="/static/plugin/jquery/jquery.cookie.js"></script>
    <script type="text/javascript">
        var csrftoken = $.cookie('csrftoken');
  
        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));      # 如過是GET|HEAD|OPTION|TRACE 這四種請求,那麼返回False
        }
        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {    # 針對非GET|HEAD|OPTION|TRACE的請求添加CSRFtoken
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });
        function Do(){
  
            $.ajax({
                url:"/app01/test/",
                data:{id:1},
                type:'POST',
                success:function(data){
                    console.log(data);
                }
            });
  
        }
    </script>
</body>
</html>

1.5 CSRF裝飾器

        在咱們的網站中,其實並非全部的站點都須要進行csrftoken驗證,那麼如何對指定的站點函數進行csrf驗證或者排除呢?

  • @csrf_protect,爲當前函數強制設置防跨站請求僞造功能,即settings中沒有設置全局中間件。
  • @csrf_exempt,取消當前函數防跨站請求僞造功能,即settings中設置了全局中間件。

使用csrf裝飾器須要先進行引用:from django.views.decorators.csrf import csrf_exempt,csrf_protect

from django.shortcuts import render,HttpResponse,redirect
from django.views.decorators.csrf import csrf_exempt,csrf_protect    # 導入csrf裝飾器
 
@csrf_exempt                    # 設置該函數取消csrf認證
def index(request):
    error_msg = ''
    if request.method == 'POST':
        user = request.POST.get('user')
        passwd = request.POST.get('passwd')
        if user == 'daxin' and passwd == '123456' or user == 'dachenzi' and passwd == '123456':
            request.session['username'] = 'daxin'
            request.session['is_login'] = True
            return redirect('/page')
        else:
            error_msg = '用戶名或密碼錯誤'
    return render(request,'index.html',{'error_msg':error_msg,'test':{'name':'daxin','age':20}})

2 Cookie&Session

        因爲HTTP協議是無狀態的協議,因此服務端須要記錄用戶的狀態時,就須要用某種機制來識具體的用戶,這個機制就是Session.典型的場景好比購物車,當你點擊下單按鈕時,因爲HTTP協議無狀態,因此並不知道是哪一個用戶操做的,因此服務端要爲特定的用戶建立了特定的Session,用用於標識這個用戶,而且跟蹤用戶,這樣才知道購物車裏面有幾本書。這個Session是保存在服務端的,有一個惟一標識。在服務端保存Session的方法不少,內存、數據庫、文件都有。集羣的時候也要考慮Session的轉移,在大型的網站,通常會有專門的Session服務器集羣,用來保存用戶會話,這個時候 Session 信息都是放在內存的,使用一些緩存服務好比Memcached之類的來放 Session。服務端如何識別特定的客戶?這個時候Cookie就登場了。每次HTTP請求的時候,客戶端都會發送相應的Cookie信息到服務端。實際上大多數的應用都是用 Cookie 來實現Session跟蹤的,第一次建立Session的時候,服務端會在HTTP協議中告訴客戶端,須要在 Cookie 裏面記錄一個Session ID,之後每次請求把這個會話ID發送到服務器,我就知道你是誰了。

用一句話總結的話:

  • Session是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據能夠保存在緩存集羣、數據庫、文件中。 (服務端存儲)
  • Cookie是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現Session的一種方式。 (客戶端存儲)

2.1 cookie/cookies

        Cookie 能夠翻譯爲「小甜品,小餅乾」 ,Cookie 在網絡系統中幾乎無處不在,當咱們瀏覽之前訪問過的網站時,網頁中可能會出現 :你好 XXX,這會讓咱們感受很親切,就好像吃了一個小甜品同樣。這實際上是經過訪問主機中的一個文件來實現的,這個文件就是 Cookie。在 Internet 中,Cookie 其實是指小量信息,是由 Web 服務器建立的,將信息存儲在用戶計算機上的文件,不一樣的網站有不一樣的cookie,因此又成爲cookies。
        當 Web 服務器建立了Cookies 後,只要在其有效期內,當用戶訪問同一個 Web 服務器時,瀏覽器首先要檢查本地的Cookies,並將其原樣發送給 Web 服務器。而當用戶結束瀏覽器會話時,系統將終止全部的 Cookie。
        目前有些 Cookie 是臨時的,有些則是持續的。臨時的 Cookie 只在瀏覽器上保存一段規定的時間,一旦超過規定的時間,該 Cookie 就會被系統清除,而持續性的能夠永久進行存儲。

PS:一個瀏覽器能建立的 Cookie 數量最多爲 300 個,而且每一個不能超過 4KB,每一個 Web 站點能設置的 Cookie 總數不能超過 20 個。

在django中使用Cookie
  如今有這樣一個場景,index頁面須要用戶首先訪問login頁面,登錄成功後才能訪問index頁面,針對這個需求,若是隻使用咱們前面介紹的知識是沒法進行完成的,而利用cookie則很是簡單。

# ------------------- views.py -------------------
 
def index(request):
    if request.method == 'GET':
        username = request.COOKIES.get('user')    # 獲取用戶請求中攜帶的cookies
        return render(request,'index.html',{'username':username})
 
def login(request):
    if request.method == 'GET':
        return render(request,'login.html')

    if request.method == 'POST':
        u = request.POST.get('username')
        p = request.POST.get('password')
        if u == 'daxin' and p == '123123':
            req = redirect('/index')
            req.set_cookie('user',u)            # 設置cookie
            return req
        else:
 
            return render(request,'login.html')

2.1.1 django中cookies用法介紹

針對cookies,django 提供了設置以及獲取的方法,而且還有可選的參數。

# 用變量接受返回用戶的信息
response = redirect('/index')
response = render(request,'index.html')
response = Httpresponse('index')
 
# 設置cookie
response.set_cookie('key','value')
 
# 獲取cookie
request.COOKIE.get('key')

set_cookie方法還提供了其餘的參數用於配置cookie:

  • key='':鍵
  • value='':值
  • max_age=None:過多少時間超時,單位是秒(默認是臨時生效,從新啓動瀏覽器失效)
  • expires=None:在某個時間後失效,是一個具體的時間。
  • path='/':Cookie生效的路徑,/ 表示根路徑(默認),根路徑的cookie能夠被當前站點的任何url的頁面訪問
  • domain=None:Cookie生效的域名
  • secure=False:https傳輸
  • httponly=False:只能http協議傳輸,沒法被JavaScript獲取(不是絕對,底層抓包能夠獲取到也能夠被覆蓋)
req.set_cookie('username','abc',max_age=10,path='/',......)

2.1.2 加密的cookies

        咱們在瀏覽器上能夠看到咱們指定的cookie中的key值,這樣不是特別安全,那麼咱們能夠對cookie進行加密。django提供cookie的加密方法。

req.set_signed_cookie('name','abc.123',salt='abc.123')                 # 設置簽名的cookie
request.get_signed_cookie('name',salt='abc.123',default='null')        # 獲取簽名的cookie

PS:salt表示要加的簽名,獲取時,必需要傳遞對應的簽名,才能夠正確獲取。

2.1.3 基於自定義分頁的實例

        一般咱們在數據列表頁面下面會看到供用戶選擇的頁面數據顯示條目數,一旦肯定之後,那麼之後選擇下一頁上一頁,將會針對用戶選擇的條目數生成新的頁碼和數據,那麼這個功能是怎麼實現的呢?如何才能讓瀏覽器記住用戶選擇的數據呢?這裏經過cookie就能實現。

# --------------------  html --------------------
 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .page {
            text-decoration: none;
            margin-right: 3px;
        }
 
        .active {
            background-color: saddlebrown;
            color: white;
        }
    </style>
</head>
<body>
<!-- 模擬數據顯示 -->
<ul>
    {% for item in data %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>
 
<div>
    <select name="count" id='count' onchange="pageChange(this)">
        <option value="10">10</option>
        <option value="30">30</option>
        <option value="50">50</option>
        <option value="100">50</option>
    </select>
</div>
{{ temp_list | safe }}
 
 
<script src="/static/jquery-3.3.1.min.js"></script>
<script src="/static/jquery.cookie.js"></script>
<script>
    $(function () {
        var val = $.cookie('page_size');
        if (val) {
            $('#count').val(val);        // 頁面加載完畢,賦值頁面數量
        }
 
    });
    function pageChange(ths) {
        var val = $(ths).val();          // 獲取用戶設置的頁面條目數
        $.cookie('page_size',val);       // 寫到cookie中(jquery.cookie),額外的屬性能夠直接根{'path':'/',......}
        location.reload()
    }
</script>
</body>
</html>
 
-------------------- views --------------------

from utils import pagination
 
def page(request):
 
    if request.method == 'GET':
        page_size = request.COOKIES.get('page_size')    # 獲取page_size
        current_page = int(request.GET.get('page', 1))
        counts = len(all_data)
        if page_size:                   # 若是用戶設置了page_size,修改生成的html
            page_obj = pagination.Page(current_page,counts,per_page_count=int(page_size))
        else:
            page_obj = pagination.Page(current_page, counts)
        data = all_data[page_obj.start:page_obj.end]
        temp_list =page_obj.page_str('/page/')
 
    return render(request,'page.html',{'data':data,'current_page':current_page,'temp_list':temp_list})

2.1.4 利用cookie進行用戶登錄檢測

        不少時候,好比咱們要訪問index頁面,須要先進行登錄,不登錄那麼拒絕訪問index頁面,這種場景下使用cookie能夠很方便的完成。

def auth(func):
    def index (request,*args,**kwargs):
        if not request.COOKIES.get('username'):     # 判斷cookie中是否存在咱們指定的key
            return redirect('/login')               # 不存在則跳轉回login頁面
        return func(request,*args,**kwargs)
    return index

PS:這裏利用裝飾器的方式對傳入的function進行cookie驗證,若是驗證失敗,那麼跳轉回login頁面,在須要cookie驗證的地方裝飾便可

@auth
def index(request):
    ... ...

擴展

  • cookie會存放在相應頭中,應答給用戶瀏覽器的,用戶瀏覽器收到後,會儲存cookie。
  • 上述設置cookie的方法其實就是對相應頭的設置,若是咱們要在相應頭中添加額外的數據,那麼可使用以下方式
req = redirect('/index')
req.set_signed_cookie('name','abc.123',salt='abc.123',max_age=10,path='/')
req['user'] = 'abc'      # 添加響應頭數據
return req

2.2 Session

        之因此要把 cookie和session放在一塊兒說,是由於它倆是有關係的,因爲cookie 是存在用戶端,並且它自己存儲的尺寸大小也有限,最關鍵是用戶能夠是可見的,並能夠隨意的修改,很不安全。那如何又要安全,又能夠方便的全局讀取信息呢?因而,這個時候,一種新的存儲會話機制:session 誕生了。session的機制是基於session_id的,它用來區分哪幾回請求是一我的發出的。爲何要這樣呢?由於HTTP是無狀態無關聯的,一個頁面可能會被成百上千人訪問,並且每一個人的用戶名是不同的,那麼服務器如何區分相同用戶訪問的呢?因此就有了找個惟一的session_id來綁定一個用戶。一個用戶在一次會話上就是一個session_id,這樣成千上萬的人訪問,服務器也能區分究竟是誰在訪問了。而session_id,就是經過cookie答覆給客戶端的,因此session依賴cookie,即解決了安全問題,又能在服務端存儲用戶相關個性化數據。

2.2.1 Django 中的 session

首先根據session的工做原理,咱們能夠大體的分析出服務端對session的處理步驟。

服務端設置session:

  1. 隨機生成字符串做爲session_id。
  2. 把session_id做爲cookie返回給用戶。
  3. 在某個地方開闢空間存儲該session_id。
  4. 將用戶相關信息存入session_id對應的字典或者其餘數據類型中。

服務端獲取session:

  1. 獲取cookie中的session_id
  2. 根據session_id獲取用戶相關的數據

2.2.2 django 中session的操做

request封裝的session,可使用相似字典的方式進行操做。

2.2.2.1 設置session

  • request.session['username'] = u :僅僅這一步就完成了上面4步的功能
  • request.session['is_login'] = True

2.2.2.2 獲取session

  • request.session['is_login']
  • request.session.get('is_login',None)
  • request.session.setdefult('k1',123)

2.2.2.3 註銷時清除用戶對應session信息

  • request.session.clear():不會刪除數據庫中的sessionid,會修改對應id的數據(多是作了標記,下次登錄會複用這個sessionid)
  • request.session.delete(request.session.session_key):會刪除數據庫中的sessionid
  • del request.session.get('username'):刪除session中的某個key對應的值

2.2.2.4 全部 鍵、值、鍵值對

  • request.session.keys()
  • request.session.values()
  • request.session.items()
  • request.session.iterkeys()
  • request.session.itervalues()
  • request.session.iteritems()

2.2.2.5 其餘操做

# 用戶session的隨機字符串
request.session.session_key
  
# 將全部session失效日期小於當前日期的數據刪除
request.session.clear_expired()
  
# 檢查 用戶的隨機字符串 在數據庫中是否存在
request.session.exists('session_key')
  
# 刪除當前用戶的全部Session數據
request.session.delete('session_key')
  
# 清空全部session
request.session.clear()
 
# django中的session默認有效期爲兩週,單獨設置某個session的超時時間須要進行以下修改
request.session.set_expiry(value)
  # 若是value是個整數(或者是個乘法表達式 60 * 60),session會在些秒數後失效。
  # 若是value是個datatime或timedelta,session就會在這個時間後失效。
  # 若是value是0,用戶關閉瀏覽器session就會失效。
  # 若是value是None,session會依賴全局session失效策略。  

PS:若是咱們在session中寫入了用戶名稱,須要在頁面上展現,並不須要在後端獲取(request.session.get('username')),在後在前端渲染({{ username }}), 由於咱們渲染頁面的同時,傳遞了request對象,request對象又包涵了咱們應答的全部信息,因此能夠在頁面上直接利用{{ request.session.username }} 進行渲染。

2.2.3 使用session進行登錄驗證

上面使用cookie 對主頁進行了登錄驗證,這裏使用session配合裝飾器,完成頁面登錄驗證。

def auth(func):
    def inner(request,*args,**kwargs):
        if request.session.get('is_login',None):
            return func(request,*args,**kwargs)
        else:
            return redirect('/login/')
 
    return inner
 
@auth
def index(request):
 
    if request.method == 'GET':
        return render(request,'index.html',{'username':request.session['username']})
 
def login(request):
 
    if request.method == 'GET':
        return render(request,'login.html')
 
    if request.method == 'POST':
        u = request.POST.get('username')
        p = request.POST.get('password')
 
        if u == 'daxin' and p == '123123':
            request.session['username'] = u
            request.session['is_login'] = True
            return redirect('/index/')
        else:
            return render(request,'login.html')

PS:django默認會把session_id和數據的對應關係放在數據庫中,通常存放在django_session表中,若是是新建的項目,須要先執行以下命令生成表纔可使用。

python3 manage.py makemigrations
python3 manage.py migrate

2.2.4 全局配置session

默認狀況下session是有默認配置的,好比默認超時時間爲兩週,若是咱們要修改全局配置,那麼須要在settings.py中進行配置

SESSION_COOKIE_NAME = "sessionid"                        # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串
SESSION_COOKIE_PATH = "/"                                # Session的cookie保存的路徑
SESSION_COOKIE_DOMAIN = None                              # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False                             # 是否Https傳輸cookie
SESSION_COOKIE_HTTPONLY = True                            # 是否Session的cookie只支持http傳輸
SESSION_COOKIE_AGE = 1209600                              # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False                   # 是否關閉瀏覽器使得Session過時
SESSION_SAVE_EVERY_REQUEST = False                        # 是否每次請求都保存Session,默認修改以後才保存,建議修改成True

PS:上面的參數都是默認配置,若是須要修改單個配置,只須要添加對應的key和value就能夠了。 

不少時候基於業務的需求,咱們須要把session放在別的地方,好比緩存或者文件中,那麼就須要使用以下配置了

SESSION_ENGINE = 'django.contrib.sessions.backends.db'     # 默認配置,表示存放在數據庫中
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 使用cache進行存儲
SESSION_CACHE_ALIAS = 'default'                            # 使用的緩存別名(默認內存緩存,也能夠是memcache),此處別名依賴緩存的設置

上面cache_alias 實際上是咱們在配置文件中指定的chache的別名,由於咱們若是要寫到memcahed中,那麼就須要寫鏈接地址以及端口,那麼就須要在cache中進行定義

CACHES = {
    'default': {      # 這裏就表示cache的別名
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}

PS:django默認支持memcached,不支持redis,須要安裝其餘插件。下面是緩存到文件中去

'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 緩存文件路徑,若是爲None,則使用tempfile模塊獲取一個臨時地址tempfile.gettempdir()    
# 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
相關文章
相關標籤/搜索