<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【JS】754- 77.9K star 的 Axios 項(xiàng)目有哪些值得借鑒的地方

          共 10816字,需瀏覽 22分鐘

           ·

          2020-10-23 18:13


          Axios 是一個(gè)基于 Promise 的 HTTP 客戶(hù)端,同時(shí)支持瀏覽器和 Node.js 環(huán)境。它是一個(gè)優(yōu)秀的 HTTP 客戶(hù)端,被廣泛地應(yīng)用在大量的 Web 項(xiàng)目中。

          由上圖可知,Axios 項(xiàng)目的 Star 數(shù)為 「77.9K」,F(xiàn)ork 數(shù)也高達(dá) 「7.3K」,是一個(gè)很優(yōu)秀的開(kāi)源項(xiàng)目,所以接下來(lái)阿寶哥將帶大家一起來(lái)分析 Axios 項(xiàng)目中一些值得借鑒的地方。

          閱讀完本文,你將了解以下內(nèi)容:

          • HTTP 攔截器的設(shè)計(jì)與實(shí)現(xiàn);
          • HTTP 適配器的設(shè)計(jì)與實(shí)現(xiàn);
          • 如何防御 CSRF 攻擊。

          下面我們從簡(jiǎn)單的開(kāi)始,先來(lái)了解一下 Axios。

          一、Axios 簡(jiǎn)介

          Axios 是一個(gè)基于 Promise 的 HTTP 客戶(hù)端,擁有以下特性:

          • 支持 Promise API;
          • 能夠攔截請(qǐng)求和響應(yīng);
          • 能夠轉(zhuǎn)換請(qǐng)求和響應(yīng)數(shù)據(jù);
          • 客戶(hù)端支持防御?CSRF 攻擊;
          • 同時(shí)支持瀏覽器和 Node.js 環(huán)境;
          • 能夠取消請(qǐng)求及自動(dòng)轉(zhuǎn)換 JSON 數(shù)據(jù)。

          在瀏覽器端 Axios 支持大多數(shù)主流的瀏覽器,比如 Chrome、Firefox、Safari 和 IE 11。此外,Axios 還擁有自己的生態(tài):

          (數(shù)據(jù)來(lái)源 —— https://github.com/axios/axios/blob/master/ECOSYSTEM.md)

          簡(jiǎn)單介紹完 Axios,我們來(lái)分析一下它提供的一個(gè)核心功能 —— 攔截器。

          二、HTTP 攔截器的設(shè)計(jì)與實(shí)現(xiàn)

          2.1 攔截器簡(jiǎn)介

          對(duì)于大多數(shù) SPA 應(yīng)用程序來(lái)說(shuō), 通常會(huì)使用 token 進(jìn)行用戶(hù)的身份認(rèn)證。這就要求在認(rèn)證通過(guò)后,我們需要在每個(gè)請(qǐng)求上都攜帶認(rèn)證信息。針對(duì)這個(gè)需求,為了避免為每個(gè)請(qǐng)求單獨(dú)處理,我們可以通過(guò)封裝統(tǒng)一的 request 函數(shù)來(lái)為每個(gè)請(qǐng)求統(tǒng)一添加 token 信息。

          但后期如果需要為某些 GET 請(qǐng)求設(shè)置緩存時(shí)間或者控制某些請(qǐng)求的調(diào)用頻率的話(huà),我們就需要不斷修改 request 函數(shù)來(lái)擴(kuò)展對(duì)應(yīng)的功能。此時(shí),如果在考慮對(duì)響應(yīng)進(jìn)行統(tǒng)一處理的話(huà),我們的 request 函數(shù)將變得越來(lái)越龐大,也越來(lái)越難維護(hù)。那么對(duì)于這個(gè)問(wèn)題,該如何解決呢?Axios 為我們提供了解決方案 —— 攔截器。

          Axios 是一個(gè)基于 Promise 的 HTTP 客戶(hù)端,而 HTTP 協(xié)議是基于請(qǐng)求和響應(yīng):

          所以 Axios 提供了請(qǐng)求攔截器和響應(yīng)攔截器來(lái)分別處理請(qǐng)求和響應(yīng),它們的作用如下:

          • 請(qǐng)求攔截器:該類(lèi)攔截器的作用是在請(qǐng)求發(fā)送前統(tǒng)一執(zhí)行某些操作,比如在請(qǐng)求頭中添加 token 字段。
          • 響應(yīng)攔截器:該類(lèi)攔截器的作用是在接收到服務(wù)器響應(yīng)后統(tǒng)一執(zhí)行某些操作,比如發(fā)現(xiàn)響應(yīng)狀態(tài)碼為 401 時(shí),自動(dòng)跳轉(zhuǎn)到登錄頁(yè)。

          在 Axios 中設(shè)置攔截器很簡(jiǎn)單,通過(guò) axios.interceptors.requestaxios.interceptors.response 對(duì)象提供的 use 方法,就可以分別設(shè)置請(qǐng)求攔截器和響應(yīng)攔截器:

          //?添加請(qǐng)求攔截器
          axios.interceptors.request.use(function?(config)?{
          ??config.headers.token?=?'added?by?interceptor';
          ??return?config;
          });

          //?添加響應(yīng)攔截器
          axios.interceptors.response.use(function?(data)?{
          ??data.data?=?data.data?+?'?-?modified?by?interceptor';
          ??return?data;
          });

          那么攔截器是如何工作的呢?在看具體的代碼之前,我們先來(lái)分析一下它的設(shè)計(jì)思路。Axios 的作用是用于發(fā)送 HTTP 請(qǐng)求,而請(qǐng)求攔截器和響應(yīng)攔截器的本質(zhì)都是一個(gè)實(shí)現(xiàn)特定功能的函數(shù)。

          我們可以按照功能把發(fā)送 HTTP 請(qǐng)求拆解成不同類(lèi)型的子任務(wù),比如有用于處理請(qǐng)求配置對(duì)象的子任務(wù),用于發(fā)送 HTTP 請(qǐng)求的子任務(wù)和用于處理響應(yīng)對(duì)象的子任務(wù)。當(dāng)我們按照指定的順序來(lái)執(zhí)行這些子任務(wù)時(shí),就可以完成一次完整的 HTTP 請(qǐng)求。

          了解完這些,接下來(lái)我們將從 「任務(wù)注冊(cè)、任務(wù)編排和任務(wù)調(diào)度」 三個(gè)方面來(lái)分析 Axios 攔截器的實(shí)現(xiàn)。

          2.2 任務(wù)注冊(cè)

          通過(guò)前面攔截器的使用示例,我們已經(jīng)知道如何注冊(cè)請(qǐng)求攔截器和響應(yīng)攔截器,其中請(qǐng)求攔截器用于處理請(qǐng)求配置對(duì)象的子任務(wù),而響應(yīng)攔截器用于處理響應(yīng)對(duì)象的子任務(wù)。要搞清楚任務(wù)是如何注冊(cè)的,就需要了解 axiosaxios.interceptors 對(duì)象。

          //?lib/axios.js
          function?createInstance(defaultConfig)?{
          ??var?context?=?new?Axios(defaultConfig);
          ??var?instance?=?bind(Axios.prototype.request,?context);

          ??//?Copy?axios.prototype?to?instance
          ??utils.extend(instance,?Axios.prototype,?context);
          ??//?Copy?context?to?instance
          ??utils.extend(instance,?context);
          ??return?instance;
          }

          //?Create?the?default?instance?to?be?exported
          var?axios?=?createInstance(defaults);

          在 Axios 的源碼中,我們找到了 axios 對(duì)象的定義,很明顯默認(rèn)的 axios 實(shí)例是通過(guò) createInstance 方法創(chuàng)建的,該方法最終返回的是 Axios.prototype.request 函數(shù)對(duì)象。同時(shí),我們發(fā)現(xiàn)了 Axios 的構(gòu)造函數(shù):

          //?lib/core/Axios.js
          function?Axios(instanceConfig)?{
          ??this.defaults?=?instanceConfig;
          ??this.interceptors?=?{
          ????request:?new?InterceptorManager(),
          ????response:?new?InterceptorManager()
          ??};
          }

          在構(gòu)造函數(shù)中,我們找到了 axios.interceptors 對(duì)象的定義,也知道了 interceptors.requestinterceptors.response 對(duì)象都是 InterceptorManager 類(lèi)的實(shí)例。因此接下來(lái),進(jìn)一步分析 InterceptorManager 構(gòu)造函數(shù)及相關(guān)的 use 方法就可以知道任務(wù)是如何注冊(cè)的:

          //?lib/core/InterceptorManager.js
          function?InterceptorManager()?{
          ??this.handlers?=?[];
          }

          InterceptorManager.prototype.use?=?function?use(fulfilled,?rejected)?{
          ??this.handlers.push({
          ????fulfilled:?fulfilled,
          ????rejected:?rejected
          ??});
          ??//?返回當(dāng)前的索引,用于移除已注冊(cè)的攔截器
          ??return?this.handlers.length?-?1;
          };

          通過(guò)觀察 use 方法,我們可知注冊(cè)的攔截器都會(huì)被保存到 InterceptorManager 對(duì)象的 handlers 屬性中。下面我們用一張圖來(lái)總結(jié)一下 Axios 對(duì)象與 InterceptorManager 對(duì)象的內(nèi)部結(jié)構(gòu)與關(guān)系:

          2.3 任務(wù)編排

          現(xiàn)在我們已經(jīng)知道如何注冊(cè)攔截器任務(wù),但僅僅注冊(cè)任務(wù)是不夠,我們還需要對(duì)已注冊(cè)的任務(wù)進(jìn)行編排,這樣才能確保任務(wù)的執(zhí)行順序。這里我們把完成一次完整的 HTTP 請(qǐng)求分為處理請(qǐng)求配置對(duì)象、發(fā)起 HTTP 請(qǐng)求和處理響應(yīng)對(duì)象 3 個(gè)階段。

          接下來(lái)我們來(lái)看一下 Axios 如何發(fā)請(qǐng)求的:

          axios({
          ??url:?'/hello',
          ??method:?'get',
          }).then(res?=>{
          ??console.log('axios?res:?',?res)
          ??console.log('axios?res.data:?',?res.data)
          })

          通過(guò)前面的分析,我們已經(jīng)知道 axios 對(duì)象對(duì)應(yīng)的是 Axios.prototype.request 函數(shù)對(duì)象,該函數(shù)的具體實(shí)現(xiàn)如下:

          //?lib/core/Axios.js
          Axios.prototype.request?=?function?request(config)?{
          ??config?=?mergeConfig(this.defaults,?config);

          ??//?省略部分代碼
          ??var?chain?=?[dispatchRequest,?undefined];
          ??var?promise?=?Promise.resolve(config);

          ??//?任務(wù)編排
          ??this.interceptors.request.forEach(function?unshiftRequestInterceptors(interceptor)?{
          ????chain.unshift(interceptor.fulfilled,?interceptor.rejected);
          ??});

          ??this.interceptors.response.forEach(function?pushResponseInterceptors(interceptor)?{
          ????chain.push(interceptor.fulfilled,?interceptor.rejected);
          ??});

          ??//?任務(wù)調(diào)度
          ??while?(chain.length)?{
          ????promise?=?promise.then(chain.shift(),?chain.shift());
          ??}

          ??return?promise;
          };

          任務(wù)編排的代碼比較簡(jiǎn)單,我們來(lái)看一下任務(wù)編排前和任務(wù)編排后的對(duì)比圖:

          2.4 任務(wù)調(diào)度

          任務(wù)編排完成后,要發(fā)起 HTTP 請(qǐng)求,我們還需要按編排后的順序執(zhí)行任務(wù)調(diào)度。在 Axios 中具體的調(diào)度方式很簡(jiǎn)單,具體如下所示:

          ?//?lib/core/Axios.js
          Axios.prototype.request?=?function?request(config)?{
          ??//?省略部分代碼
          ??var?promise?=?Promise.resolve(config);
          ??while?(chain.length)?{
          ????promise?=?promise.then(chain.shift(),?chain.shift());
          ??}
          }

          因?yàn)?chain 是數(shù)組,所以通過(guò) while 語(yǔ)句我們就可以不斷地取出設(shè)置的任務(wù),然后組裝成 Promise 調(diào)用鏈從而實(shí)現(xiàn)任務(wù)調(diào)度,對(duì)應(yīng)的處理流程如下圖所示:

          下面我們來(lái)回顧一下 Axios 攔截器完整的使用流程:

          //?添加請(qǐng)求攔截器?——?處理請(qǐng)求配置對(duì)象
          axios.interceptors.request.use(function?(config)?{
          ??config.headers.token?=?'added?by?interceptor';
          ??return?config;
          });

          //?添加響應(yīng)攔截器?——?處理響應(yīng)對(duì)象
          axios.interceptors.response.use(function?(data)?{
          ??data.data?=?data.data?+?'?-?modified?by?interceptor';
          ??return?data;
          });

          axios({
          ??url:?'/hello',
          ??method:?'get',
          }).then(res?=>{
          ??console.log('axios?res.data:?',?res.data)
          })

          介紹完 Axios 的攔截器,我們來(lái)總結(jié)一下它的優(yōu)點(diǎn)。Axios 通過(guò)提供攔截器機(jī)制,讓開(kāi)發(fā)者可以很容易在請(qǐng)求的生命周期中自定義不同的處理邏輯。

          此外,也可以通過(guò)攔截器機(jī)制來(lái)靈活地?cái)U(kuò)展 Axios 的功能,比如 Axios 生態(tài)中列舉的 axios-response-logger 和 axios-debug-log 這兩個(gè)庫(kù)。

          參考 Axios 攔截器的設(shè)計(jì)模型,我們就可以抽出以下通用的任務(wù)處理模型:

          三、HTTP 適配器的設(shè)計(jì)與實(shí)現(xiàn)

          3.1 默認(rèn) HTTP 適配器

          Axios 同時(shí)支持瀏覽器和 Node.js 環(huán)境,對(duì)于瀏覽器環(huán)境來(lái)說(shuō),我們可以通過(guò) XMLHttpRequestfetch API 來(lái)發(fā)送 HTTP 請(qǐng)求,而對(duì)于 Node.js 環(huán)境來(lái)說(shuō),我們可以通過(guò) Node.js 內(nèi)置的 httphttps 模塊來(lái)發(fā)送 HTTP 請(qǐng)求。

          為了支持不同的環(huán)境,Axios 引入了適配器。在 HTTP 攔截器設(shè)計(jì)部分,我們看到了一個(gè) dispatchRequest 方法,該方法用于發(fā)送 HTTP 請(qǐng)求,它的具體實(shí)現(xiàn)如下所示:

          //?lib/core/dispatchRequest.js
          module.exports?=?function?dispatchRequest(config)?{
          ??//?省略部分代碼
          ??var?adapter?=?config.adapter?||?defaults.adapter;
          ??
          ??return?adapter(config).then(function?onAdapterResolution(response)?{
          ????//?省略部分代碼
          ????return?response;
          ??},?function?onAdapterRejection(reason)?{
          ????//?省略部分代碼
          ????return?Promise.reject(reason);
          ??});
          };

          通過(guò)查看以上的 dispatchRequest 方法,我們可知 Axios 支持自定義適配器,同時(shí)也提供了默認(rèn)的適配器。對(duì)于大多數(shù)場(chǎng)景,我們并不需要自定義適配器,而是直接使用默認(rèn)的適配器。因此,默認(rèn)的適配器就會(huì)包含瀏覽器和 Node.js 環(huán)境的適配代碼,其具體的適配邏輯如下所示:

          //?lib/defaults.js
          var?defaults?=?{
          ??adapter:?getDefaultAdapter(),
          ??xsrfCookieName:?'XSRF-TOKEN',
          ??xsrfHeaderName:?'X-XSRF-TOKEN',
          ??//...
          }

          function?getDefaultAdapter()?{
          ??var?adapter;
          ??if?(typeof?XMLHttpRequest?!==?'undefined')?{
          ????//?For?browsers?use?XHR?adapter
          ????adapter?=?require('./adapters/xhr');
          ??}?else?if?(typeof?process?!==?'undefined'?&&?
          ????Object.prototype.toString.call(process)?===?'[object?process]')?{
          ????//?For?node?use?HTTP?adapter
          ????adapter?=?require('./adapters/http');
          ??}
          ??return?adapter;
          }

          getDefaultAdapter 方法中,首先通過(guò)平臺(tái)中特定的對(duì)象來(lái)區(qū)分不同的平臺(tái),然后再導(dǎo)入不同的適配器,具體的代碼比較簡(jiǎn)單,這里就不展開(kāi)介紹。

          3.2 自定義適配器

          其實(shí)除了默認(rèn)的適配器外,我們還可以自定義適配器。那么如何自定義適配器呢?這里我們可以參考 Axios 提供的示例:

          var?settle?=?require('./../core/settle');
          module.exports?=?function?myAdapter(config)?{
          ??//?當(dāng)前時(shí)機(jī)點(diǎn):
          ??//??-?config配置對(duì)象已經(jīng)與默認(rèn)的請(qǐng)求配置合并
          ??//??-?請(qǐng)求轉(zhuǎn)換器已經(jīng)運(yùn)行
          ??//??-?請(qǐng)求攔截器已經(jīng)運(yùn)行
          ??
          ??//?使用提供的config配置對(duì)象發(fā)起請(qǐng)求
          ??//?根據(jù)響應(yīng)對(duì)象處理Promise的狀態(tài)
          ??return?new?Promise(function(resolve,?reject)?{
          ????var?response?=?{
          ??????data:?responseData,
          ??????status:?request.status,
          ??????statusText:?request.statusText,
          ??????headers:?responseHeaders,
          ??????config:?config,
          ??????request:?request
          ????};

          ????settle(resolve,?reject,?response);

          ????//?此后:
          ????//??-?響應(yīng)轉(zhuǎn)換器將會(huì)運(yùn)行
          ????//??-?響應(yīng)攔截器將會(huì)運(yùn)行
          ??});
          }

          在以上示例中,我們主要關(guān)注轉(zhuǎn)換器、攔截器的運(yùn)行時(shí)機(jī)點(diǎn)和適配器的基本要求。比如當(dāng)調(diào)用自定義適配器之后,需要返回 Promise 對(duì)象。這是因?yàn)?Axios 內(nèi)部是通過(guò) Promise 鏈?zhǔn)秸{(diào)用來(lái)完成請(qǐng)求調(diào)度,不清楚的小伙伴可以重新閱讀 “攔截器的設(shè)計(jì)與實(shí)現(xiàn)” 部分的內(nèi)容。

          現(xiàn)在我們已經(jīng)知道如何自定義適配器了,那么自定義適配器有什么用呢?在 Axios 生態(tài)中,阿寶哥發(fā)現(xiàn)了 axios-mock-adapter 這個(gè)庫(kù),該庫(kù)通過(guò)自定義適配器,讓開(kāi)發(fā)者可以輕松地模擬請(qǐng)求。對(duì)應(yīng)的使用示例如下所示:

          var?axios?=?require("axios");
          var?MockAdapter?=?require("axios-mock-adapter");

          //?在默認(rèn)的Axios實(shí)例上設(shè)置mock適配器
          var?mock?=?new?MockAdapter(axios);

          //?模擬?GET?/users?請(qǐng)求
          mock.onGet("/users").reply(200,?{
          ??users:?[{?id:?1,?name:?"John?Smith"?}],
          });

          axios.get("/users").then(function?(response)?{
          ??console.log(response.data);
          });

          對(duì) MockAdapter 感興趣的小伙伴,可以自行了解一下 axios-mock-adapter 這個(gè)庫(kù)。到這里我們已經(jīng)介紹了 Axios 的攔截器與適配器,下面阿寶哥用一張圖來(lái)總結(jié)一下 Axios 使用請(qǐng)求攔截器和響應(yīng)攔截器后,請(qǐng)求的處理流程:

          四、CSRF 防御

          4.1 CSRF 簡(jiǎn)介

          「跨站請(qǐng)求偽造」(Cross-site request forgery),通常縮寫(xiě)為 「CSRF」 或者 「XSRF」, 是一種挾制用戶(hù)在當(dāng)前已登錄的 Web 應(yīng)用程序上執(zhí)行非本意的操作的攻擊方法。

          跨站請(qǐng)求攻擊,簡(jiǎn)單地說(shuō),是攻擊者通過(guò)一些技術(shù)手段欺騙用戶(hù)的瀏覽器去訪問(wèn)一個(gè)自己曾經(jīng)認(rèn)證過(guò)的網(wǎng)站并運(yùn)行一些操作(如發(fā)郵件,發(fā)消息,甚至財(cái)產(chǎn)操作如轉(zhuǎn)賬和購(gòu)買(mǎi)商品)。由于瀏覽器曾經(jīng)認(rèn)證過(guò),所以被訪問(wèn)的網(wǎng)站會(huì)認(rèn)為是真正的用戶(hù)操作而去運(yùn)行。

          為了讓小伙伴更好地理解上述的內(nèi)容,阿寶哥畫(huà)了一張跨站請(qǐng)求攻擊示例圖:

          在上圖中攻擊者利用了 Web 中用戶(hù)身份驗(yàn)證的一個(gè)漏洞:「簡(jiǎn)單的身份驗(yàn)證只能保證請(qǐng)求發(fā)自某個(gè)用戶(hù)的瀏覽器,卻不能保證請(qǐng)求本身是用戶(hù)自愿發(fā)出的」。既然存在以上的漏洞,那么我們應(yīng)該怎么進(jìn)行防御呢?接下來(lái)我們來(lái)介紹一些常見(jiàn)的 CSRF 防御措施。

          4.2 CSRF 防御措施

          4.2.1 檢查 Referer 字段

          HTTP 頭中有一個(gè) Referer 字段,這個(gè)字段用以標(biāo)明請(qǐng)求來(lái)源于哪個(gè)地址。「在處理敏感數(shù)據(jù)請(qǐng)求時(shí),通常來(lái)說(shuō),Referer 字段應(yīng)和請(qǐng)求的地址位于同一域名下」

          以示例中商城操作為例,Referer 字段地址通常應(yīng)該是商城所在的網(wǎng)頁(yè)地址,應(yīng)該也位于 www.semlinker.com 之下。而如果是 CSRF 攻擊傳來(lái)的請(qǐng)求,Referer 字段會(huì)是包含惡意網(wǎng)址的地址,不會(huì)位于 www.semlinker.com 之下,這時(shí)候服務(wù)器就能識(shí)別出惡意的訪問(wèn)。

          這種辦法簡(jiǎn)單易行,僅需要在關(guān)鍵訪問(wèn)處增加一步校驗(yàn)。但這種辦法也有其局限性,因其完全依賴(lài)瀏覽器發(fā)送正確的 Referer 字段。雖然 HTTP 協(xié)議對(duì)此字段的內(nèi)容有明確的規(guī)定,但并無(wú)法保證來(lái)訪的瀏覽器的具體實(shí)現(xiàn),亦無(wú)法保證瀏覽器沒(méi)有安全漏洞影響到此字段。并且也存在攻擊者攻擊某些瀏覽器,篡改其 Referer 字段的可能。

          4.2.2 同步表單 CSRF 校驗(yàn)

          CSRF 攻擊之所以能夠成功,是因?yàn)榉?wù)器無(wú)法區(qū)分正常請(qǐng)求和攻擊請(qǐng)求。針對(duì)這個(gè)問(wèn)題我們可以要求所有的用戶(hù)請(qǐng)求都攜帶一個(gè) CSRF 攻擊者無(wú)法獲取到的 token。對(duì)于 CSRF 示例圖中的表單攻擊,我們可以使用 「同步表單 CSRF 校驗(yàn)」 的防御措施。

          「同步表單 CSRF 校驗(yàn)」 就是在返回頁(yè)面時(shí)將 token 渲染到頁(yè)面上,在 form 表單提交的時(shí)候通過(guò)隱藏域或者作為查詢(xún)參數(shù)把 CSRF token 提交到服務(wù)器。比如,在同步渲染頁(yè)面時(shí),在表單請(qǐng)求中增加一個(gè) _csrf 的查詢(xún)參數(shù),這樣當(dāng)用戶(hù)在提交這個(gè)表單的時(shí)候就會(huì)將 CSRF token 提交上來(lái):

          <form?method="POST"?action="/upload?_csrf={{由服務(wù)端生成}}"?enctype="multipart/form-data">
          ??用戶(hù)名:?<input?name="name"?/>
          ??選擇頭像:?<input?name="file"?type="file"?/>
          ??<button?type="submit">提交button>
          form>
          4.2.3 雙重 Cookie 防御

          「雙重 Cookie 防御」 就是將 token 設(shè)置在 Cookie 中,在提交(POST、PUT、PATCH、DELETE)等請(qǐng)求時(shí)提交 Cookie,并通過(guò)請(qǐng)求頭或請(qǐng)求體帶上 Cookie 中已設(shè)置的 token,服務(wù)端接收到請(qǐng)求后,再進(jìn)行對(duì)比校驗(yàn)。

          下面我們以 jQuery 為例,來(lái)看一下如何設(shè)置 CSRF token:

          let?csrfToken?=?Cookies.get('csrfToken');

          function?csrfSafeMethod(method)?{
          ??//?以下HTTP方法不需要進(jìn)行CSRF防護(hù)
          ??return?(/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
          }

          $.ajaxSetup({
          ??beforeSend:?function(xhr,?settings)?{
          ????if?(!csrfSafeMethod(settings.type)?&&?!this.crossDomain)?{
          ??????xhr.setRequestHeader('x-csrf-token',?csrfToken);
          ????}
          ??},
          });

          介紹完 CSRF 攻擊的方式和防御手段,最后我們來(lái)看一下 Axios 是如何防御 CSRF 攻擊的。

          4.3 Axios CSRF 防御

          Axios 提供了 xsrfCookieNamexsrfHeaderName 兩個(gè)屬性來(lái)分別設(shè)置 CSRF 的 Cookie 名稱(chēng)和 HTTP 請(qǐng)求頭的名稱(chēng),它們的默認(rèn)值如下所示:

          //?lib/defaults.js
          var?defaults?=?{
          ??adapter:?getDefaultAdapter(),

          ??//?省略部分代碼
          ??xsrfCookieName:?'XSRF-TOKEN',
          ??xsrfHeaderName:?'X-XSRF-TOKEN',
          };

          前面我們已經(jīng)知道在不同的平臺(tái)中,Axios 使用不同的適配器來(lái)發(fā)送 HTTP 請(qǐng)求,這里我們以瀏覽器平臺(tái)為例,來(lái)看一下 Axios 如何防御 CSRF 攻擊:

          //?lib/adapters/xhr.js
          module.exports?=?function?xhrAdapter(config)?{
          ??return?new?Promise(function?dispatchXhrRequest(resolve,?reject)?{
          ????var?requestHeaders?=?config.headers;
          ????
          ????var?request?=?new?XMLHttpRequest();
          ????//?省略部分代碼
          ????
          ????//?添加xsrf頭部
          ????if?(utils.isStandardBrowserEnv())?{
          ??????var?xsrfValue?=?(config.withCredentials?||?isURLSameOrigin(fullPath))?&&?config.xsrfCookieName??
          ????????cookies.read(config.xsrfCookieName)?:
          ????????undefined;

          ??????if?(xsrfValue)?{
          ????????requestHeaders[config.xsrfHeaderName]?=?xsrfValue;
          ??????}
          ????}

          ????request.send(requestData);
          ??});
          };

          看完以上的代碼,相信小伙伴們就已經(jīng)知道答案了,原來(lái) Axios 內(nèi)部是使用 「雙重 Cookie 防御」 的方案來(lái)防御 CSRF 攻擊。

          好的,到這里本文的主要內(nèi)容都已經(jīng)介紹完了,其實(shí) Axios 項(xiàng)目還有一些值得我們借鑒的地方,比如 CancelToken 的設(shè)計(jì)、異常處理機(jī)制等,感興趣的小伙伴可以自行學(xué)習(xí)一下。

          五、參考資源

          • Github - axios
          • 維基百科 - 跨站請(qǐng)求偽造
          • Egg - 安全威脅 CSRF 的防范
          推薦閱讀
          從 12.9K 的前端開(kāi)源項(xiàng)目我學(xué)到了啥?

          從 12.9K 的前端開(kāi)源項(xiàng)目我學(xué)到了啥?

          聚焦全棧,專(zhuān)注分享 TypeScript、Web API、前端架構(gòu)等技術(shù)干貨。

          瀏覽 50
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  开心五月婷 | 少妇福利在线 | 一区二区在线视频 | 亚洲天堂资源 | 精品中文字幕视频 |