原文: http://feclub.cn/post/content...html
這裏再也不介紹CSRF,已經瞭解CSRF原理的同窗能夠直接跳到:「三、先後端分離下有何不一樣?」。前端
不太瞭解的同窗能夠看這兩篇對CSRF介紹比較詳細的參考文章:vue
CSRF 攻擊的應對之道webpack
淺談CSRF攻擊方式nginx
若是來不及瞭解CSRF的原理,能夠這麼理解:有一我的發給你一個搞(mei)笑(nv)圖片連接,你打開這個連接以後,便馬上收到了短信:你的銀行裏的錢已經轉移到這我的的賬戶了。git
上面這個例子固然有點危言聳聽,固然能夠肯定的是確實會有這樣的漏洞:你打開了一個未知域名的連接,而後你就自動發了條廣告帖子、你的Gmail的郵件內容就泄露了、你的百度登陸狀態就沒了……github
防護方案在上面的兩篇文章裏也有提到,總結下,無外乎三種:web
用戶操做限制,好比驗證碼;ajax
請求來源限制,好比限制HTTP Referer才能完成操做;算法
token驗證機制,好比請求數據字段中添加一個token,響應請求時校驗其有效性;
第一種方案明顯嚴重影響了用戶體驗,並且還有額外的開發成本;第二種方案成本最低,可是並不能保證100%安全,並且頗有可能會埋坑;第三種方案,可取!
token驗證的CSRF防護機制是公認最合適的方案,也是本文討論的重點。
《CSRF 攻擊的應對之道》這篇文章裏有提到:
要把全部請求都改成 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的
咱們前端架構早已經告別了服務端語言(PHP/JAVA等)綁定路由、攜帶數據渲染模板引擎的方式(畢竟是2011年的文章了,咱們笑而不語)。
固然, 前端不要高興的太早:先後端分離以後,Nodejs不具有完善的服務端SESSION、數據庫等功能。
總結一下,在「更先進」的前端架構下,與以往的架構會有一些區別:
Nodejs層不處理SESSION,沒法直接實現會話狀態數據保存;
全部的數據經過Ajax異步獲取,能夠靈活實現token方案;
如上文提到,這裏僅僅討論在「更先進」的前端後端架構背景下的token防護方案的實現。
token防護的總體思路是:
第一步:後端隨機產生一個token,把這個token保存在SESSION狀態中;同時,後端把這個token交給前端頁面;
第二步:下次前端須要發起請求(好比發帖)的時候把這個token加入到請求數據或者頭信息中,一塊兒傳給後端;
第三步:後端校驗前端請求帶過來的token和SESSION裏的token是否一致;
上文提到過,先後端分離狀態下,Nodejs是不具有SESSION功能的。那這種token防護機制是否是就沒法實現了呢?
確定不是。咱們能夠藉助cookie把這個流程升級下:
第一步:後端隨機產生一個token,基於這個token經過SHA-56等散列算法生成一個密文;
第二步:後端將這個token和生成的密文都設置爲cookie,返回給前端;
第三步:前端須要發起請求的時候,從cookie中獲取token,把這個token加入到請求數據或者頭信息中,一塊兒傳給後端;
第四步:後端校驗cookie中的密文,以及前端請求帶過來的token,進行正向散列驗證;
固然這樣實現也有須要注意的:
散列算法都是須要計算的,這裏會有性能風險;
token參數必須由前端處理以後交給後端,而不能直接經過cookie;
cookie更臃腫,會不可避免地讓頭信息更重;
如今方案肯定了,具體該如何實現呢?
咱們的技術棧是 koa(服務端)
+ Vue.js(前端)
。有興趣能夠看這些資料:
在服務端,實現了一個token生成的中間件,koa-grace-csrf:
// 注意:代碼有作精簡 const tokens = require('./lib/tokens'); return function* csrf(next) { let curSecret = this.cookies.get('密文的cookie'); // 其餘若是要獲取參數,則爲配置參數值 let curToken = '請求http頭信息中的token'; // token不存在 if (!curToken || !curSecret) { return this.throw('CSRF Token Not Found!',403) } // token校驗失敗 if (!tokens.verify(curSecret, curToken)) { return this.throw('CSRF token Invalid!',403) } yield next; // 不管何種狀況都種兩個cookie // cookie_key: 當前token的cookie_key,httpOnly let secret = tokens.secretSync(); this.cookies.set(options.cookie_key, secret); // cookie_token: 當前token的的content,不須要httpOnly let newToken = tokens.create(secret); this.cookies.set(options.cookie_token, newToken) }
在前端代碼中,對發送ajax請求的封裝稍做優化:
this.$http.post(url, data, { headers: { 'http請求頭信息字段名': 'cookie中的token' } }).then((res) => {})
總結一下:
Nodejs生成一個隨機數,經過隨機數生成散列密文;並將隨機數和密文存到cookie;
客戶端JS獲取cookie中的隨機數,經過http頭信息交給Nodejs;
Nodejs響應請求,校驗cookie中的密文和頭信息中的隨機數是否匹配;
這裏依舊有個細節值得提一下:Nodejs的上層通常是nginx,而nginx默認會過濾頭信息中不合法的字段(好比頭信息字段名包含「_」的),這裏在寫頭信息的時候須要注意。
上文也提到,經過cookie及http頭信息傳遞加密token會有不少弊端;有沒有更優雅的實現方案呢?
回溯下CSRF產生的根本緣由:cookie會被第三方發起的跨站請求攜帶,這本質上是HTTP協議設計的漏洞。
那麼,咱們能不能經過cookie的某個屬性禁止cookie的這個特性呢?
好消息是,在最新的RFC規範中已經加入了「samesite」屬性。細節這裏再也不贅述,能夠參考:
固然,目前爲止,客戶端對samesite屬性的支持並非特別好;回到先後端分離架構下,咱們明確下先後端分離框架的基本原則:
後端(Java / PHP )職責:
服務層顆粒化接口,以便前端Nodejs層異步併發調用;
用戶狀態保存,實現用戶權限等各類功能;
前端(Nodejs + Javascript)職責:
Nodejs層完成路由託管及模板引擎渲染功能
Nodejs層不負責實現任何SESSION和數據庫功能
咱們提到,前端Nodejs層不負責實現任何SESSION和數據庫功能
,但有沒有可能把後端緩存系統作成公共服務提供給Nodejs層使用呢?想一想感受前端整條路都亮了有木有?!這裏先挖一個坑,後續慢慢填。
這裏再順便提一下,新架構下的XSS防護。
猶記得,在狼廠使用PHP的年代,常常被安所有門曝出各種XSS漏洞,而後就在smaty裏添加各類escape
濾鏡,可是添加以後發現居然把原始數據也給轉義了。
固然,如今更多要歸功於各類MVVM
單頁面應用:使得前端徹底不須要經過讀取URL中的參數來控制VIEW。
不過,還有一點值得一提:先後端分離框架下,路由由Nodejs控制;我本身要獲取的後端參數和須要用在業務邏輯的參數,在主觀上前端同窗更好把握一些。
因此, 在koa(服務端)
+ Vue.js(前端)
架構下基本不用顧慮XSS問題(至少不會被全安組追着問XSS漏洞啥時候修復)。
要不學PHP、看Java、玩Python作全棧好了?