CORS 為什么要區(qū)分『簡(jiǎn)單請(qǐng)求』和『預(yù)檢請(qǐng)求』

原文

CORS(cross-origin resource sharing),跨源資源共享(一般俗稱(chēng)『跨域請(qǐng)求』),想必大家都已經(jīng)有基本的了解。如果你還不了解的話,可以閱讀MDN 上的介紹 (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) ,這里就不贅述了。

不過(guò)在學(xué)習(xí)CORS時(shí),有些朋友會(huì)有疑惑,為什么CORS要把請(qǐng)求分成兩類(lèi):簡(jiǎn)單請(qǐng)求和預(yù)檢請(qǐng)求(preflighted requests)呢?

如果我們看簡(jiǎn)單請(qǐng)求和預(yù)檢請(qǐng)求的區(qū)分,會(huì)看到有很多的條件:

簡(jiǎn)單請(qǐng)求的 HTTP 方法只能是 GET、HEAD 或 POST

簡(jiǎn)單請(qǐng)求的 HTTP 頭只能是 Accept/Accept-Language/Conent-Language/Content-Type 等

簡(jiǎn)單請(qǐng)求的 Content-Type 頭只能是 text/plain、multipart/form-data 或 application/x-www-form-urlencoded

看上去很是復(fù)雜。

那么怎么理解這些限制呢?

其實(shí),簡(jiǎn)單請(qǐng)求就是普通 HTML Form 在不依賴腳本的情況下可以發(fā)出的請(qǐng)求,比如表單的 method 如果指定為 POST ,可以用 enctype 屬性指定用什么方式對(duì)表單內(nèi)容進(jìn)行編碼,合法的值就是前述這三種。

非簡(jiǎn)單請(qǐng)求就是普通 HTML Form 無(wú)法實(shí)現(xiàn)的請(qǐng)求。比如 PUT 方法、需要其他的內(nèi)容編碼方式、自定義頭之類(lèi)的。

對(duì)于服務(wù)器來(lái)說(shuō),第一,許多服務(wù)器壓根沒(méi)打算給跨源用。當(dāng)然你不給 CORS 響應(yīng)頭,瀏覽器也不會(huì)使用響應(yīng)結(jié)果,但是請(qǐng)求本身可能已經(jīng)造成了后果。所以最好是默認(rèn)禁止跨源請(qǐng)求。

第二,要回答某個(gè)請(qǐng)求是否接受跨源,可能涉及額外的計(jì)算邏輯。這個(gè)邏輯可能很簡(jiǎn)單,比如一律放行。也可能比較復(fù)雜,結(jié)果可能取決于哪個(gè)資源哪種操作來(lái)自哪個(gè) origin。對(duì)瀏覽器來(lái)說(shuō),就是某個(gè)資源是否允許跨源這么簡(jiǎn)單;對(duì)服務(wù)器來(lái)說(shuō),計(jì)算成本卻可大可小。所以我們希望最好不用每次請(qǐng)求都讓服務(wù)器勞神計(jì)算。

CORS-preflight 就是這樣一種機(jī)制,瀏覽器先單獨(dú)請(qǐng)求一次,詢問(wèn)服務(wù)器某個(gè)資源是否可以跨源,如果不允許的話就不發(fā)實(shí)際的請(qǐng)求。注意先許可再請(qǐng)求等于默認(rèn)禁止了跨源請(qǐng)求。如果允許的話,瀏覽器會(huì)記住,然后發(fā)實(shí)際請(qǐng)求,且之后每次就都直接請(qǐng)求而不用再詢問(wèn)服務(wù)器否可以跨源了。于是,服務(wù)器想支持跨源,就只要針對(duì) preflight 進(jìn)行跨源許可計(jì)算。本身真正的響應(yīng)代碼則完全不管這個(gè)事情。并且因?yàn)?preflight 是許可式的,也就是說(shuō)如果服務(wù)器不打算接受跨源,什么事情都不用做。

但是這機(jī)制只能限于非簡(jiǎn)單請(qǐng)求。在處理簡(jiǎn)單請(qǐng)求的時(shí)候,如果服務(wù)器不打算接受跨源請(qǐng)求,不能依賴 CORS-preflight 機(jī)制。因?yàn)椴煌ㄟ^(guò) CORS,普通表單也能發(fā)起簡(jiǎn)單請(qǐng)求,所以默認(rèn)禁止跨源是做不到的。

既然如此,簡(jiǎn)單請(qǐng)求發(fā) preflight 就沒(méi)有意義了,就算發(fā)了服務(wù)器也省不了后續(xù)每次的計(jì)算,反而在一開(kāi)始多了一次 preflight。

有些人把簡(jiǎn)單請(qǐng)求不需要 preflight 理解為『向下兼容』。這也不能說(shuō)錯(cuò)。但嚴(yán)格來(lái)說(shuō),并不是『為了向下兼容』而不能發(fā)。理論上瀏覽器可以區(qū)別對(duì)待表單請(qǐng)求和非表單請(qǐng)求 —— 對(duì)傳統(tǒng)的跨源表單提交不發(fā) preflight,從而保持兼容,只對(duì)非表單跨源請(qǐng)求發(fā) preflight。

但這樣做并沒(méi)有什么好處,反而把事情搞復(fù)雜了。比如本來(lái)你可以直接用腳本發(fā)跨源普通請(qǐng)求,盡管(在服務(wù)器默認(rèn)沒(méi)有跨源處理的情況下)你無(wú)法得到響應(yīng)結(jié)果,但是你的需求可能只是發(fā)送無(wú)需返回,比如打個(gè)日志。但現(xiàn)在如果服務(wù)器不理解 preflight 你就干不了這個(gè)事情了。

而且如果真的這樣做,服務(wù)器就變成了默認(rèn)允許跨源表單,如果想控制跨源,還是得(跟原本一樣)直接在響應(yīng)處理中執(zhí)行跨源計(jì)算邏輯;另一方面服務(wù)器又需要增加對(duì) preflight 請(qǐng)求的響應(yīng)支持,執(zhí)行類(lèi)似的跨源計(jì)算邏輯以控制來(lái)自非表單的相同跨源請(qǐng)求。服務(wù)器通常沒(méi)有區(qū)分表單/非表單差異的需求,這樣搞純粹是折騰服務(wù)器端工程師。

所以簡(jiǎn)單請(qǐng)求不發(fā) preflight 不是因?yàn)椴荒芗嫒荩且驗(yàn)榧嫒莸那疤嵯掳l(fā) preflight 對(duì)絕大多數(shù)服務(wù)器應(yīng)用來(lái)說(shuō)沒(méi)有意義,反而把問(wèn)題搞復(fù)雜了。

  • 補(bǔ)充1
    賀師俊 (作者) :
    絕對(duì)意義上的后端安全是另一個(gè)層面的事情,不要混在一起理解。簡(jiǎn)單請(qǐng)求不存在『繞過(guò)』的問(wèn)題。從有網(wǎng)站開(kāi)始,簡(jiǎn)單請(qǐng)求就一直可以跨源提交。所以服務(wù)器如果要禁止簡(jiǎn)單請(qǐng)求的跨源提交,從來(lái)就是要自己處理的。而節(jié)省跨源計(jì)算只能針對(duì)新的需求,也就是原本瀏覽器不可能發(fā)送的跨源非簡(jiǎn)單請(qǐng)求。你確實(shí)可以把簡(jiǎn)單請(qǐng)求不需要preflight理解為『為了向下兼容』。但嚴(yán)格來(lái)說(shuō),不是。理論上瀏覽器可以對(duì)傳統(tǒng)的跨源表單提交不發(fā)preflight,從而保持兼容,只對(duì)腳本發(fā)起的跨源表單提交發(fā)preflight。這樣服務(wù)器這里默認(rèn)允許跨源表單提交,但通過(guò)響應(yīng)preflight來(lái)控制腳本的相同跨源請(qǐng)求。但是服務(wù)器通常沒(méi)有區(qū)分這種微秒差異的需求。所以不發(fā)preflight不是因?yàn)椴荒芗嫒荩且驗(yàn)榧嫒莸那疤嵯掳l(fā)preflight對(duì)絕大多數(shù)服務(wù)器應(yīng)用來(lái)說(shuō)沒(méi)有意義。

*補(bǔ)充2
Ivony:
如果我們現(xiàn)在重新設(shè)計(jì)整個(gè)HTTP協(xié)議,我們可以要求瀏覽器在發(fā)送任何數(shù)據(jù)到另外一個(gè)域的服務(wù)器之前,都必須先發(fā)送preflight request。但是大部分現(xiàn)存網(wǎng)站并未針對(duì)preflight request做出實(shí)現(xiàn),所以這意味著現(xiàn)有的互聯(lián)網(wǎng)中,如果一個(gè)域的表單向另一個(gè)域提交的時(shí)候會(huì)跨域失敗,直到目標(biāo)網(wǎng)站更新處理perflight request為止。所以在我們制定這一新的標(biāo)準(zhǔn)的時(shí)候,應(yīng)當(dāng)考慮到目前互聯(lián)網(wǎng)已經(jīng)存在這樣的請(qǐng)求,他們雖然看起來(lái)可能不安全,但為了向下兼容,我們不能強(qiáng)制對(duì)這些請(qǐng)求做preflight request。既然不能強(qiáng)制做preflight request驗(yàn)證,那發(fā)這個(gè)東西就沒(méi)有什么意義了。當(dāng)然,我認(rèn)為在時(shí)機(jī)成熟的時(shí)候,我們可以引入一種強(qiáng)制CORS的機(jī)制,就像現(xiàn)在的強(qiáng)制HTTPS機(jī)制一樣。我們可以約定瀏覽器預(yù)先發(fā)一個(gè)請(qǐng)求到目標(biāo)域名確定目標(biāo)域的服務(wù)器是否支持強(qiáng)制CORS。如果目標(biāo)域支持強(qiáng)制CORS,則瀏覽器對(duì)引用目標(biāo)域的任何資源請(qǐng)求都發(fā)出Origin頭,任何數(shù)據(jù)的發(fā)送都先發(fā)送preflight request。至于你的迷惑,簡(jiǎn)單來(lái)說(shuō):CORS是允許受限的跨域訪問(wèn),不是限制現(xiàn)有的跨域訪問(wèn)。沒(méi)有CORS之前我們不是不可以跨域訪問(wèn),而是要很彎彎繞(譬如說(shuō)JSONP和萬(wàn)能的服務(wù)器代發(fā)),而CORS則是提出一個(gè)方案可以讓我們直接了當(dāng)?shù)拿枋隹缬蛟L問(wèn)的需求并且加以控制。
*補(bǔ)充3
失禮:
簡(jiǎn)單請(qǐng)求的情況不需求發(fā)起preflight,也就不用先發(fā)起options請(qǐng)求然后再發(fā)起真實(shí)的請(qǐng)求,也就是說(shuō)如果剛好允許跨域或者壓根沒(méi)跨域,它消耗資源少。簡(jiǎn)單請(qǐng)求的那些情況就是我們常用的操作,如果也需要preflight,就浪費(fèi)資源了。因?yàn)閷?duì)我們自己而言的大量非跨域請(qǐng)求就不公平了。而對(duì)于非簡(jiǎn)單請(qǐng)求而言,瀏覽器實(shí)行跨域預(yù)檢機(jī)制可以節(jié)約資源,也做了一道防線。因?yàn)槿绻蠖瞬辉试S跨域,就不需要發(fā)送正式的請(qǐng)求啦。退一步講,正常后端都是會(huì)對(duì)跨域請(qǐng)求做過(guò)濾限制的。不管你是簡(jiǎn)單還是復(fù)雜請(qǐng)求。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,786評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,986評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,204評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,964評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,354評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,554評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,106評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,918評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,093評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,342評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,755評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,009評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,839評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,107評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容

  • ??2005 年,Jesse James Garrett 發(fā)表了一篇在線文章,題為“Ajax: A new App...
    霜天曉閱讀 897評(píng)論 0 1
  • 前沿: 最近總聽(tīng)到同事聊跨域得問(wèn)題,于是自己抽空仔細(xì)的查閱了一下關(guān)于跨域的知識(shí)。說(shuō)到跨域,就得提到同源,跨域是指一...
    戈弋圖閱讀 1,825評(píng)論 0 4
  • 前言:對(duì)于跨域請(qǐng)求,很早之前就有去了解過(guò),但因?yàn)橐恢标P(guān)注的都是服務(wù)器后端開(kāi)發(fā),故也就僅僅停留在概念的理解上而沒(méi)有機(jī)...
    ken_ljq閱讀 89,834評(píng)論 6 128
  • 題目1.什么是同源策略? 同源策略(Same origin Policy): 瀏覽器出于安全方面的考慮,只允許與本...
    FLYSASA閱讀 1,743評(píng)論 0 6
  • 引用自HTTP訪問(wèn)控制(CORS) 當(dāng) Web 資源請(qǐng)求由其它域名或端口提供的資源時(shí),會(huì)發(fā)起跨域 HTTP 請(qǐng)求(...
    有涯逐無(wú)涯閱讀 2,600評(píng)論 0 4