前言
用過Django 進行開發的同學都知道,Django框架天然支持對CSRF攻擊的防護,因為其內置了一個名為CsrfViewMiddleware的中間件,其基於Cookie方式的防護原理,相比基於session的方式,更適合目前前后端分離的業務場景,但美中不足的是,其生成的csrf_token在一個session周期中是不變,這對於一些特定的業務場景,顯然有點遺憾。
為了彌補這個遺憾,本文介紹一種不用修改CsrfViewMiddleware中間件源碼的方式,實現基於請求的csrf_token更新方式,詳文如下。
實現過程
1、Django csrf 校驗的兩個場景
在業務場景中,有兩種不同的csrf防護場景,一種是基於Form 表單提交數據的防護,一種是基於ajax 異步請求數據的防護。
對於Form 表單,可以通過在表單中內置`{% csrf_token %}` 實現在提交數據時一起攜帶csrf_token提交上去,從而通過后端csrf 校驗。
這里順便提下 Django模板引擎是如何渲染`{% csrf_token %}`的,其實就是將`{% csrf_token %}` 替換成csrf_input的返回值(這點可從Django 模板引擎源碼中找到),如下:
而get_token則是從request.META['CSRF_COOKIE']中獲取:
對於ajax 請求,需要在提交請求的時候,添加一個名為x-csrftoken的頭部(這個頭部是Django源碼中內定的),值為從cookie中提取的指定name的值,這個name可自定義,比如下圖為`csrf-bastion`:
2、Django csrf token 的生成流程
主要關注下CsrfViewMiddleware 中間件的process_view和process_response。
process_view的主要功能之一就是從請求的cookie中提取指定name的(通過settings.CSRF_COOKIE_NAME指定)cookie值為:csrf token,然后賦值給request.META['CSRF_COOKIE'],如果從請求頭中提取不到,則重新生成,如下圖:
process_response 中會有更新csrf token cookie的功能,如下:
3、 通過在視圖中修改request.META['CSRF_COOKIE']值實現csrf token 的更新
通過1和2我們就可以知道只要在在response返回之前更新request.META['CSRF_COOKIE']的值,便可以實現每次請求的csrf token 都不一樣,當然可以通過修改CsrfViewMiddleware中間件源碼的方式實現,不過這種方式的入侵性太大,最好的選擇是在視圖中修改,因為視圖處理流程是在response之前進行,如下:
這里的rotate_token則是CsrfViewMiddleware 中間件提供的更新csrf token的接口,如下:
總結
本文提到的這個方法確實可以實現基於請求對csrf_token值進行更新,而不是原先的基於session的,這樣更新之后,對於ajax請求倒是沒有什么問題,不過對於From表單中csrf_token值的更新是需要進行后端渲染更新的,對於前后端分離的請求,這個不足是可以讓后端提供個token 獲取接口來實現前端頁面中form表單的csrf_token值更新實現,當然也可以對form表單的提交行為進行監聽,然后異步提交,這樣直接走ajax那條路線,就沒啥問題了。