Django 進階篇之 CSRF,COOIKE,SESSION

1.CSRF(Cross Site Request Forgery, 跨站域請求僞造)

CSRF 背景與介紹

CSRF(Cross Site Request Forgery, 跨站域請求僞造)是一種網絡的攻擊方式,它在 2007 年曾被列爲互聯網 20 大安全隱患之一。其餘安全隱患,好比 SQL 腳本注入,跨站域腳本攻擊等在近年來已經逐漸爲衆人熟知,不少網站也都針對他們進行了防護。然而,對於大多數人來講,CSRF 卻依然是一個陌生的概念。即使是大名鼎鼎的 Gmail, 在 2007 年末也存在着 CSRF 漏洞,從而被黑客攻擊而使 Gmail 的用戶形成巨大的損失。javascript

CSRF 攻擊實例

CSRF 攻擊能夠在受害者絕不知情的狀況下以受害者名義僞造請求發送給受攻擊站點,從而在並未受權的狀況下執行在權限保護之下的操做。好比說,受害者 Bob 在銀行有一筆存款,經過對銀行的網站發送請求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可使 Bob 把 1000000 的存款轉到 bob2 的帳號下。一般狀況下,該請求發送到網站後,服務器會先驗證該請求是否來自一個合法的 session,而且該 session 的用戶 Bob 已經成功登錄。黑客 Mallory 本身在該銀行也有帳戶,他知道上文中的 URL 能夠把錢進行轉賬操做。Mallory 能夠本身發送一個請求給銀行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。可是這個請求來自 Mallory 而非 Bob,他不能經過安全認證,所以該請求不會起做用。這時,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 則能夠拿到錢後逍遙法外。html

CSRF 攻擊的對象

在討論如何抵禦 CSRF 以前,先要明確 CSRF 攻擊的對象,也就是要保護的對象。從以上的例子可知,CSRF 攻擊是黑客藉助受害者的 cookie 騙取服務器的信任,可是黑客並不能拿到 cookie,也看不到 cookie 的內容。另外,對於服務器返回的結果,因爲瀏覽器同源策略的限制,黑客也沒法進行解析。所以,黑客沒法從返回的結果中獲得任何東西,他所能作的就是給服務器發送請求,以執行請求中所描述的命令,在服務器端直接改變數據的值,而非竊取服務器中的數據。因此,咱們要保護的對象是那些能夠直接產生數據改變的服務,而對於讀取數據的服務,則不須要進行 CSRF 的保護。好比銀行系統中轉帳的請求會直接改變帳戶的金額,會遭到 CSRF 攻擊,須要保護。而查詢餘額是對金額的讀取操做,不會改變數據,CSRF 攻擊沒法解析服務器返回的結果,無需保護。java

防護策略:在請求地址中添加 token 並驗證

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

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

Django 中使用CSRF

method:

To take advantage of CSRF protection in your views, follow these steps:

The CSRF middleware is activated by default in the MIDDLEWARE_CLASSES setting. If you override that setting, remember that 'django.middleware.csrf.CsrfViewMiddleware' should come before any view middleware that assume that CSRF attacks have been dealt with.

If you disabled it, which is not recommended, you can use csrf_protect() on particular views you want to protect (see below).

In any template that uses a POST form, use the csrf_token tag inside the <form> element if the form is for an internal URL, e.g.:

<form action="" method="post">{% csrf_token %}
This should not be done for POST forms that target external URLs, since that would cause the CSRF token to be leaked, leading to a vulnerability.

In the corresponding view functions, ensure that RequestContext is used to render the response so that {%csrf_token %} will work properly. If you’re using the render() function, generic views, or contrib apps, you are covered already since these all use RequestContext.

 

CSRF with AJAX

While the above method can be used for AJAX POST requests, it has some inconveniences: you have to remember to pass the CSRF token in as POST data with every POST request. For this reason, there is an alternative method: on each XMLHttpRequest, set a custom X-CSRFToken header to the value of the CSRF token. This is often easier, because many JavaScript frameworks provide hooks that allow headers to be set on every request.

As a first step, you must get the CSRF token itself. The recommended source for the token is the csrftokencookie, which will be set if you’ve enabled CSRF protection for your views as outlined above.

 

Acquiring the token is straightforward:程序員

// using jQuery
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');

 The above code could be simplified by using the JavaScript Cookie library to replace getCookie:github

var csrftoken = Cookies.get('csrftoken');

 

Finally, you’ll have to actually set the header on your AJAX request, while protecting the CSRF token from being sent to other domains using settings.crossDomain in jQuery 1.5.1 and newer:ajax

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);
        }
    }
});

 

下面來看下實例:數據庫

此前咱們將settings的csrf註釋掉,如今打開django

MIDDLEWARE_CLASSES = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

 

views:

def csrf(request):

    return render(request,'csrf.html')

 

html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
        {#        form提交,能夠比喻爲衣服的外兜#}
        <form action="/csrf/" method="post">
            {% csrf_token %}
            <input type="text" name="v" />
            <input type="submit" value="提交" />
        </form>
        {#Ajax提交,能夠比喻爲衣服的內兜#}
        <input type="button"  value="Ajax提交" onclick="DoAjax();" />
        <script src="/static/jquery-2.1.4.min.js"></script>
        <script src="/static/jquery.cookie.js"></script> ##在引用$.cooike前要導入jquery.cooike.js這個插件
    <script>
        // 去cookie中獲取值
        var csrftoken = $.cookie('csrftoken');

        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }
        {#        爲每一個jquery函數註冊csrf方法,不用每次都從新註冊#}
        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });
        function DoAjax(){
            $.ajax({
                url: '/csrf/',
                type: 'POST',
                data: {'k1': 'v1'},
                success: function (data) {
                    console.log(data);
                }
            })
        }
    </script>

</body>
</html>

 

開發模式下打開在input標籤下django會自動添加帶有token的input標籤

 

全局:

  中間件 django.middleware.csrf.CsrfViewMiddleware

局部:

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

注:from django.views.decorators.csrf import csrf_exempt,csrf_protect

取消csrftoken驗證

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def post_data(request):
    pass

在urlconf當中用

from django.views.decorators.csrf import csrf_exempt
urlpatterns = [
    url(r'^post/get_data/$', csrf_exempt(post_data), name='post_data'),

]

只對特定的url作csrf保護,能夠用csrf_protect裝飾器

緣由是在視圖函數中少了一個裝飾方法@csrf_protect
裝飾方法:
而不是增長CsrfViewMiddleware就全面保護,您可使用csrfprotect裝飾,具備徹底相同的功能,在特定的視圖,須要保護。注意必須經過POST形式和{% csrftoken %}令牌才能對此有用。
from django.views.decorators.csrf import csrf_protect

@csrf_protect
def post_data(request):
    pass

更多:https://docs.djangoproject.com/en/dev/ref/csrf/#ajax

2.cookie&&session

Cookie就是一段字符串,保存於本機電腦上。

session 保存於服務器,用來保存用戶的會話信息,依賴於Cookies.

一、流程

舉個例子,我們在登陸一個網站後,拿JD,淘寶舉例,若是我登陸進去以後,在想點擊訂單的時候。server斷怎麼判斷我是「我」,而不是其餘人呢?

Http是短鏈接,那麼Server端確定有一個保存我登陸狀態的地方(session),那server怎麼判斷是我發送過來的請求呢?就是經過Cookie!

當客戶端訪問過來後,server端會在IE裏生成一個Cookie,當訪問過來的時候就能夠經過Cookie進行判斷

二、結構

一、自動生成一段字符串

二、將字符串發送到客戶端的瀏覽器,同時把字符串當作key放在session裏。(能夠理解爲session就是一個字典)

三、在用戶的session對應的value裏設置任意值

 

3.Session和Cookie的好處及用途

使用Session和Cookie的好處:Cookie能夠理解爲一個身份證ID,你只能拿着他去和Server端進行通訊,若是你沒有這個ID那麼server端也不知道你是誰!

實例:(0 0 !)

我在寫博客的時候在作Cookie和Session的實驗,把Cookie刪掉了!當我保存的時候直接給我提出來了,爲何呢?就是由於,server端不知道我是誰了,我已經沒有密鑰了。

 

因此,只要Session和Cookie任意一方失效,就能夠理解爲:

Cookie失效就至關於身份證ID過時,須要從新認證才能夠繼續使用。Session失效就至關於銀行裏的數據標識此ID無效,也須要從新申請。

4.操做與配置

Cooike:

一、獲取Cookie:

request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
    參數:
        default: 默認值
           salt: 加密鹽
        max_age: 後臺控制過時時間

二、設置Cookie:

rep = HttpResponse(...) 或 rep = render(request, ...)
 
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密鹽',...)
    參數:
        key,              鍵
        value='',         值
        max_age=None,     超時時間
        expires=None,     超時時間(IE requires expires, so set it if hasn't been already.)
        path='/',         Cookie生效的路徑,/ 表示根路徑,特殊的:跟路徑的cookie能夠被任何url的頁面訪問
        domain=None,      Cookie生效的域名
        secure=False,     https傳輸
        httponly=False    只能http協議傳輸,沒法被JavaScript獲取(不是絕對,底層抓包能夠獲取到也能夠被覆蓋)

因爲cookie保存在客戶端的電腦上,因此,JavaScript和jquery也能夠操做cookie。

<script src='/static/js/jquery.cookie.js'></script>
$.cookie("list_pager_num", 30,{ path: '/' });

 實例:

views:

def cookie(request):
    print(request.COOKIES)
    obj = render(request, 'cookie.html')
    obj.set_cookie('k3','v3',path='/cookie/')
    return obj

def cookie1(request):
    print(request.COOKIES)
    obj = render(request, 'cookie.html')
    return obj

 

 session:

Django中默認支持Session,其內部提供了5種類型的Session供開發者使用:

  • 數據庫(默認)
  • 緩存
  • 文件
  • 緩存+數據庫
  • 加密cookie

一、數據庫Session

Django默認支持Session,而且默認是將Session數據存儲在數據庫中,即:django_session 表中。
 
a. 配置 settings.py
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默認)
     
    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,默認修改以後才保存(默認)
 
 
 
b. 使用
 
    def index(request):
        # 獲取、設置、刪除Session中數據
        request.session['k1']
        request.session.get('k1',None)
        request.session['k1'] = 123
        request.session.setdefault('k1',123) # 存在則不設置
        del request.session['k1']
 
        # 全部 鍵、值、鍵值對
        request.session.keys()
        request.session.values()
        request.session.items()
        request.session.iterkeys()
        request.session.itervalues()
        request.session.iteritems()
 
 
        # 用戶session的隨機字符串
        request.session.session_key
 
        # 將全部Session失效日期小於當前日期的數據刪除
        request.session.clear_expired()
 
        # 檢查 用戶session的隨機字符串 在數據庫中是否
        request.session.exists("session_key")
 
        # 刪除當前用戶的全部Session數據
        request.session.delete("session_key")
     
     request.session.set_expiry(value)
     * 若是value是個整數,session會在些秒數後失效。
     * 若是value是個datatime或timedelta,session就會在這個時間後失效。
     * 若是value是0,用戶關閉瀏覽器session就會失效。
     * 若是value是None,session會依賴全局session失效策略。
  
...

 二、緩存Session

 
    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
    SESSION_CACHE_ALIAS = 'default'                            # 使用的緩存別名(默認內存緩存,也能夠是memcache),此處別名依賴緩存的設置
 
 
    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,默認修改以後才保存
 
 
 
b. 使用
 
    同上

 三、文件Session

a. 配置 settings.py
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
    SESSION_FILE_PATH = None                                    # 緩存文件路徑,若是爲None,則使用tempfile模塊獲取一個臨時地址tempfile.gettempdir()                                                            # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
 
 
    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,默認修改以後才保存
 
b. 使用
 
    同上

 四、緩存+數據庫Session

數據庫用於作持久化,緩存用於提升效率
 
a. 配置 settings.py
 
    SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎
 
b. 使用
 
    同上

 五、加密cookie Session

a. 配置 settings.py
     
    SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎
 
b. 使用
 
    同上

 更多參考:猛擊這裏 和 猛擊這裏

擴展:Session用戶驗證

def login(func):
    def wrap(request, *args, **kwargs):
        # 若是未登錄,跳轉到指定頁面
        if request.path == '/test/':
            return redirect('http://www.baidu.com')
        return func(request, *args, **kwargs)
    return wrap

實例:

USER_LIST = ['alex','eric','wangminglong', 'hu']

def session_login(request):
    if request.method == 'POST':
        u = request.POST.get('user')
        p = request.POST.get('pwd')
        if p == '123' and u in USER_LIST:
            request.session['user'] = u
            return redirect('/session_index/')
    return render(request,'session_login.html')
##定義驗證裝飾器def auth(func):    def inner(request, *args,**kwargs):        # print(request,args,kwargs)        ##判斷用戶是否登陸        user = request.session.get('user', None)        if not user:            return redirect('/session_login/')        return func(request, *args,**kwargs)    return inner@authdef session_index(request):    user = request.session.get('user', None)    return render(request,'session_index.html',{'user': user})@authdef session_logout(request):    del request.session['user']    return redirect('/session_login/')
相關文章
相關標籤/搜索