偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Cors跨域(一):深入理解跨域請(qǐng)求概念及其根因

開發(fā) 前端
本文用很長(zhǎng)的篇幅介紹了Cors跨域資源共享的相關(guān)知識(shí),并且用代碼做了示范,希望能助你通關(guān)Cors這個(gè)狗皮膏藥一樣粘著我們的硬核知識(shí)點(diǎn)。本文文字?jǐn)⑹鲚^多,介紹了同源、跨域、Cors的幾乎所有概念,雖然略顯難啃,但這些是指導(dǎo)我們實(shí)踐的說(shuō)明書。

[[405056]]

前言

你好,我是YourBatman。

做Web開發(fā)的小伙伴對(duì)“跨域”定并不陌生,像狗皮膏藥一樣粘著幾乎每位同學(xué),對(duì)它可謂既愛又恨。跨域請(qǐng)求之于創(chuàng)業(yè)、小型公司來(lái)講是個(gè)頭疼的問(wèn)題,因?yàn)檫@類企業(yè)還未沉淀出一套行之有效的、統(tǒng)一的解決方案。

讓人擔(dān)憂的是,據(jù)我了解不少程序員同學(xué)(不乏有高級(jí)開發(fā))碰到跨域問(wèn)題大都一頭霧水:

然后很自然的 用谷歌去百度一下搜索答案,但相關(guān)文章可能參差不齊、魚龍混雜。短則半天長(zhǎng)則一天(包含改代碼、部署等流程)此問(wèn)題才得以解決,一個(gè)“小小跨域”問(wèn)題成功偷走你的寶貴時(shí)間。

既然跨域是個(gè)如此常見(特別是當(dāng)下前后端分離的開發(fā)模式),因此深入理解CORS變得就異常的重要了(反倒前端工程師不用太了解),因此早在2019年我剛開始寫博客那會(huì)就有過(guò)較為詳細(xì)的系列文章:

現(xiàn)在把它搬到公眾號(hào)形成技術(shù)專欄,并且加點(diǎn)料,讓它更深、更全面、更系統(tǒng)的幫助到你,希望可以助你從此不再怕Cors跨域資源共享問(wèn)題。

本文提綱

版本約定

  • JDK:8
  • Servlet:4.x

正文

文章遵循一貫的風(fēng)格,本文將采用概念 + 代碼示例的方式,層層遞進(jìn)的進(jìn)行展開敘述。那么上菜,先來(lái)個(gè)示例預(yù)覽,模擬一下跨域請(qǐng)求,后面的一些的概念示例將以此作為抓手。

模擬跨域請(qǐng)求

要模擬跨域請(qǐng)求的根本是需要兩個(gè)源:讓請(qǐng)求的來(lái)源和目標(biāo)源不一樣。這里我就使用IDEA作為靜態(tài)Web服務(wù)器(63342),Tomcat作為后端動(dòng)態(tài)Servlet服務(wù)器(8080)。

❝說(shuō)明:服務(wù)器都在本機(jī),端口不一樣即可❞

前端代碼

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <title>CORS跨域請(qǐng)求</title> 
  6.     <!--導(dǎo)入Jquery--> 
  7.     <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script> 
  8. </head> 
  9. <body> 
  10. <button id="btn">跨域從服務(wù)端獲取內(nèi)容</button> 
  11. <div id="content"></div> 
  12.  
  13. <script> 
  14.     $("#btn").click(function () { 
  15.         // 跨域請(qǐng)求 
  16.         $.get("http://localhost:8080/cors"function (result) { 
  17.             $("#content").append(result).append("<br/>"); 
  18.         }); 
  19.  
  20.         // 同域請(qǐng)求 
  21.         $.get("http://localhost:63342"); 
  22.         $.post("http://localhost:63342"); 
  23.     }); 
  24.  
  25. </script> 
  26. </body> 
  27. </html> 

 使用IDEA作為靜態(tài)web服務(wù)器,瀏覽器輸入地址即可訪問(wèn)(注:端口號(hào)為63342):

后端代碼

后端寫個(gè)Servlet來(lái)接收cors請(qǐng)求

  1. /** 
  2.  * 在此處添加備注信息 
  3.  * 
  4.  * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a> 
  5.  * @site https://yourbatman.cn 
  6.  * @date 2021/6/9 10:36 
  7.  * @since 0.0.1 
  8.  */ 
  9. @Slf4j 
  10. @WebServlet(urlPatterns = "/cors"
  11. public class CorsServlet extends HttpServlet { 
  12.  
  13.     @Override 
  14.     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
  15.         String requestURI = req.getRequestURI(); 
  16.         String method = req.getMethod(); 
  17.         String originHeader = req.getHeader("Origin"); 
  18.  
  19.         log.info("收到請(qǐng)求:{},方法:{}, Origin頭:{}", requestURI, method, originHeader); 
  20.         resp.getWriter().write("hello cors..."); 
  21.     } 

啟動(dòng)后端服務(wù)器,點(diǎn)擊頁(yè)面上的按鈕,結(jié)果如下:

服務(wù)端控制臺(tái)輸出:

  1. ... INFO  c.y.cors.servlet.CorsServlet - 收到請(qǐng)求:/cors,方法:GET, Origin頭:http://localhost:63342 

❝服務(wù)端輸出日志,說(shuō)明即使前端的Http Status是error,但服務(wù)端還是收到并處理了這個(gè)請(qǐng)求的❞下面以此代碼示例為基礎(chǔ),普及一下和Cors跨域資源共享相關(guān)的概念。

Host、Referer、Origin的區(qū)別

這哥三看起來(lái)很是相似,下面對(duì)概念作出區(qū)分。

1.Host:去哪里。域名+端口。值為客戶端將要訪問(wèn)的遠(yuǎn)程主機(jī),瀏覽器在發(fā)送Http請(qǐng)求時(shí)會(huì)帶有此Header

2.Referer:來(lái)自哪里。協(xié)議+域名+端口+路徑+參數(shù)。當(dāng)前請(qǐng)求的來(lái)源頁(yè)面的地址,服務(wù)端一般使用 Referer 首部識(shí)別訪問(wèn)來(lái)源,可能會(huì)以此進(jìn)行統(tǒng)計(jì)分析、日志記錄以及緩存優(yōu)化等

  • 來(lái)源頁(yè)面協(xié)議為File或者Data URI(如頁(yè)面從本地打開的)
  • 來(lái)源頁(yè)面是Https,而目標(biāo)URL是http
  • 瀏覽器地址欄直接輸入網(wǎng)址訪問(wèn),或者通過(guò)瀏覽器的書簽直接訪問(wèn)
  • 使用JS的location.href跳轉(zhuǎn)
  • ...

常見應(yīng)用場(chǎng)景:百度的搜索廣告就會(huì)分析Referer來(lái)判斷打開站點(diǎn)是從百度搜索跳轉(zhuǎn)的,還是直接URL輸入地址的

一般情況下瀏覽器會(huì)帶有此Header,但這些case不會(huì)帶有Referer這個(gè)頭

3.Origin:來(lái)自哪里(跨域)。協(xié)議+域名+端口。它用于Cors請(qǐng)求和同域POST請(qǐng)求

可以看到Referer與Origin功能相似,前者一般用于統(tǒng)計(jì)和阻止盜鏈,后者用于CORS請(qǐng)求。但是還是有幾點(diǎn)不同:

1.只有跨域請(qǐng)求,或者同域時(shí)發(fā)送post請(qǐng)求,才會(huì)攜帶Origin請(qǐng)求頭;而Referer只要瀏覽器能獲取到都會(huì)攜帶(除了上面說(shuō)明的幾種case外)

2.若瀏覽器不能獲取到請(qǐng)求源頁(yè)面地址(如上面的幾種case),Referer頭不會(huì)發(fā)送,但Origin依舊會(huì)發(fā)送,只是值是null而已(注:雖然值為null,但此請(qǐng)求依舊屬于Cors請(qǐng)求哦),如下圖所示:

3.Origin的值只包括協(xié)議、域名和端口,而Rerferer不但包括協(xié)議、域名、端口還包括路徑,參數(shù),注意不包括hash值

瀏覽器的同源策略

瀏覽器的職責(zé)是展示/渲染document、css、script腳本等,但是這些資源(將document、css、script統(tǒng)一稱為資源)可能來(lái)自不同的地方,如本地、遠(yuǎn)程服務(wù)器、甚至黑客的服務(wù)器......瀏覽器作為萬(wàn)維網(wǎng)的入口,是我們接入互聯(lián)網(wǎng)最重要的軟件之一(甚至沒有之一),因此它的安全性顯得尤為重要,這就出現(xiàn)了瀏覽器的同源策略。

同源策略是瀏覽器一個(gè)重要的安全策略,它用于限制一個(gè)origin源的document或者它加載的腳本如何能與另一個(gè)origin源的資源進(jìn)行交互。它能幫助阻隔惡意文檔,減少(并不是杜絕)可能被攻擊的媒介。

❝方便和安全往往是相悖的:安全性增高了,方便性就會(huì)有所降低❞那么問(wèn)題來(lái)了,什么才算同源?

同源的定義

URL被稱作:統(tǒng)一資源定位符,同源是針對(duì)URL而言的。一個(gè)完整的URL各部分如下圖所示:

❝Tips:域名和host是等同的概念,域名+端口號(hào) = host+端口號(hào)(大部分情況下你看到域名并沒有端口號(hào),那是采用了默認(rèn)端口號(hào)80而已)❞同源:只和上圖的前兩部分(protocol + domain)有關(guān),規(guī)則為:全部相同則為同源。這個(gè)定義不難理解,但有幾點(diǎn)需要再?gòu)?qiáng)調(diào)一下:

  • 兩部分必須完全一樣才算同源
  • 這里的domain包含port端口號(hào),所以總共是兩部分而非三部分

當(dāng)然也有說(shuō)三部分的(協(xié)議+host+port),理解其含義就成

下面通過(guò)舉例來(lái)徹底了解下。譬如,我的源URL為:http://www.baidu.com/api/user,下面表格描述了不同URL的各類情況:

不同源的網(wǎng)絡(luò)訪問(wèn)

瀏覽器同源策略的存在,限制了不同源之間的交互,實(shí)為不便。但是瀏覽器也開了一些“綠燈”,讓其不受同源策略的約束。此種情況一般可分為如下三類:

1.跨域?qū)懖僮?Cross-origin writes):一般是被允許的。如鏈接(如a標(biāo)簽)、重定向以及表單提交(如form表單的提交)

2.跨域資源嵌入(Cross-origin embedding):一般是允許的。比如下面這些例子:

  • <script src="..."></script>標(biāo)簽嵌入js腳本
  • <link rel="stylesheet" href="...">標(biāo)簽嵌入CSS
  • <img>展示的圖片
  • <video>和<audio>媒體資源
  • <object>、 <embed> 、<applet>嵌入的插件
  • CSS中使用@font-face引入字體
  • 通過(guò)<iframe>載入資源

3.跨域讀操作(Cross-origin reads):一般是不被允許的。比如我們的http接口請(qǐng)求等都屬于此范疇,也是本專欄關(guān)注的焦點(diǎn)

簡(jiǎn)單總結(jié)成一句話:瀏覽器自己是可以發(fā)起跨域請(qǐng)求的(比如a標(biāo)簽、img標(biāo)簽、form表單等),但是Javascript是不能去跨域獲取資源(如ajax)。

如何允許不同源的網(wǎng)絡(luò)訪問(wèn)

上面說(shuō)到的第三種情況:跨域讀操作一般是不允許跨域訪問(wèn)的,而這種情況是我們開發(fā)過(guò)程中最關(guān)心、最常見的case,因此必須解決。

❝Tips:這里的讀指的是廣義上的讀,指的是從服務(wù)器獲取資源(有response)的都叫讀操作,而和具體是什么Http Method無(wú)關(guān)。換句話講,所有的Http API接口請(qǐng)求都在這里都指的是讀操作❞可以使用 CORS 來(lái)允許跨源訪問(wèn)。CORS 是 HTTP 的一部分,它允許服務(wù)端來(lái)指定哪些主機(jī)可以從這個(gè)服務(wù)端加載資源。

什么是Cors跨域

Cors(Cross-origin resource sharing):跨域資源共享,它是瀏覽器的一個(gè)技術(shù)規(guī)范,由W3C規(guī)定,規(guī)范的wiki地址在此:https://www.w3.org/wiki/CORS_Enabled#What_is_CORS_about.3F

❝話外音:它是瀏覽器的一種(自我保護(hù))行為,并且已形成規(guī)范。也就是說(shuō):backend請(qǐng)求backend是不存在此現(xiàn)象的嘍❞若想實(shí)現(xiàn)Cors機(jī)制的跨域請(qǐng)求,是需要瀏覽器和服務(wù)器同時(shí)支持的。關(guān)于瀏覽器對(duì)CORS的支持情況:現(xiàn)在都2021年了,so可以認(rèn)為100%的瀏覽器都是支持的,再加上CORS的整個(gè)過(guò)程都由瀏覽器自動(dòng)完成,前端無(wú)需做任何設(shè)置,所以前端工程師的ajax原來(lái)怎么用現(xiàn)在還是怎么用,它對(duì)前段開發(fā)人員是完全透明的。

為何需要Cors跨域訪問(wèn)?

瀏覽器費(fèi)盡心思的搞個(gè)同源策略來(lái)保護(hù)我們的安全,但為何又需要跨域來(lái)打破這種安全策略呢?其實(shí)啊,這一切都和互聯(lián)網(wǎng)的快速發(fā)展有關(guān)~

隨著Web開放的程度越來(lái)越高,頁(yè)面的內(nèi)容也是越來(lái)越豐富。因此頁(yè)面上出現(xiàn)的元素也就越來(lái)越多:圖片、視頻、各種文字內(nèi)容等。為了分而治之,一個(gè)頁(yè)面的內(nèi)容可能來(lái)自不同地方,也就是不同的domain域,因此通過(guò)API跨域訪問(wèn)成了必然。

瀏覽器作為進(jìn)入Internet最大的入口,很長(zhǎng)時(shí)間它是個(gè)大互聯(lián)公司的必爭(zhēng)之地,因此市面上并存的瀏覽器種類繁多且魚龍混扎:IE 7、8、9、10,Chrome、Safari、火狐,每個(gè)瀏覽器對(duì)跨域的實(shí)現(xiàn)可能都不一樣。因此對(duì)開發(fā)者而言亟待需要一個(gè)規(guī)范的、統(tǒng)一方案,它就是Cors。

CORS(Cross-Origin Resource Sharing)由W3C組織于2009-03-17編寫工作草案,直到2014-01-16才正式畢業(yè)成為行業(yè)規(guī)范,所有瀏覽器得以遵守。至此,程序員同學(xué)們?cè)诮鉀Q跨域問(wèn)題上,只需按照Cors規(guī)范實(shí)施即可。

Cors的工作原理

Web資源涉及到兩個(gè)角色:瀏覽器(消費(fèi)者)和服務(wù)器(提供者),面向這兩個(gè)角色來(lái)了解Cors的原理非常簡(jiǎn)單,如下圖所示:

1.若瀏覽器發(fā)送的是個(gè)跨域請(qǐng)求,http請(qǐng)求中就會(huì)攜帶一個(gè)名為Origin的頭表明自己的“位置”,如Origin: http://localhost:5432

2.服務(wù)端接到請(qǐng)求后,就可以根據(jù)傳過(guò)來(lái)的Origin頭做邏輯,決定是否要將資源共享給這個(gè)源嘍。而這個(gè)決定通過(guò)響應(yīng)頭Access-Control-Allow-Origin來(lái)承載,它的value值可以是任意值,有如下情況:

  • 值為*,通配符,允許所有的Origin共享此資源
  • 值為http://localhost:5432(也就是和Origin相同),共享給此Origin
  • 值為非http://localhost:5432(也就是和Origin不相同),不共享給此Origin
  • 無(wú)此頭:不共享給此origin
  • 有此頭:值有如下可能情況

3.瀏覽器接收到Response響應(yīng)后,會(huì)去提取Access-Control-Allow-Origin這個(gè)頭。然后根據(jù)上述規(guī)則來(lái)決定要接收此響應(yīng)內(nèi)容還是拒絕

❝Tips:Access-Control-Allow-Origin響應(yīng)頭只能有1個(gè),且value值就是個(gè)字符串。另外,value值即使寫為http://aa.com,http://bb.com這種也屬于一個(gè)而非兩個(gè)值❞

Cors細(xì)粒度控制:授權(quán)響應(yīng)頭

在Cors規(guī)范中,除了可以通過(guò)Access-Control-Allow-Origin響應(yīng)頭來(lái)對(duì)主體資源(URL級(jí)別)進(jìn)行授權(quán)外,還提供了針對(duì)于具體響應(yīng)頭更細(xì)粒度的控制,這個(gè)響應(yīng)頭就是:Access-Control-Expose-Headers。換句話講,該頭用于規(guī)定哪些響應(yīng)頭(們)可以暴露給前端,默認(rèn)情況下這6個(gè)響應(yīng)頭無(wú)需特別的顯示指定就支持:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

若不在此值里面的頭將不會(huì)返回給前端(其實(shí)返回了,只是瀏覽器讓其對(duì)前端不可見了而已,對(duì)JavaScript也不可見哦)。

但是,但是,但是,這種細(xì)粒度控制header的機(jī)制對(duì)簡(jiǎn)單請(qǐng)求是無(wú)效的,只針對(duì)于非簡(jiǎn)單請(qǐng)求(也叫復(fù)雜請(qǐng)求)。由此可見,將哪些類型的跨域資源請(qǐng)求劃分為簡(jiǎn)單請(qǐng)求的范疇就顯得特備重要了。

何為簡(jiǎn)單請(qǐng)求

Cors規(guī)范定義簡(jiǎn)單請(qǐng)求的原則是:請(qǐng)求不是以更新(添加、修改和刪除)資源為目的,服務(wù)端對(duì)請(qǐng)求的處理不會(huì)導(dǎo)致自身維護(hù)資源的改變。對(duì)于簡(jiǎn)單跨域資源請(qǐng)求來(lái)說(shuō),瀏覽器將兩個(gè)步驟(取得授權(quán)和獲取資源)合二為一,由于不涉及到資源的改變,所以不會(huì)帶來(lái)任何副作用。

對(duì)于一個(gè)請(qǐng)求,必須同時(shí)符合如下要求才被劃為簡(jiǎn)單請(qǐng)求:

1.Http Method只能為其一:

  • GET
  • POST
  • HEAD

2.請(qǐng)求頭只能在如下范圍:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain
  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type,其中它的值必須如下其一:

除此之外的請(qǐng)求都為非簡(jiǎn)單請(qǐng)求(也可稱為復(fù)雜請(qǐng)求)。非簡(jiǎn)單請(qǐng)求可能對(duì)服務(wù)端資源改變,因此Cors規(guī)定瀏覽器在發(fā)出此類請(qǐng)求之前必須有一個(gè)“預(yù)檢(Preflight)”機(jī)制,這也就是我們熟悉的OPTIONS請(qǐng)求。

什么是Preflight預(yù)檢機(jī)制

顧名思義,它表示在瀏覽器發(fā)出真正請(qǐng)求之前,先發(fā)送一個(gè)預(yù)檢請(qǐng)求,這個(gè)在Http里就是OPTIONS請(qǐng)求方式。這個(gè)請(qǐng)求很特殊,它不包含主體(無(wú)請(qǐng)求參數(shù)、請(qǐng)求體等),主要就是將一些憑證、授權(quán)相關(guān)的輔助信息放在請(qǐng)求頭里交給服務(wù)器去做決策。因此它除了攜帶Origin請(qǐng)求頭外,還會(huì)額外攜帶如下兩個(gè)請(qǐng)求頭:

  • Access-Control-Request-Method:真正請(qǐng)求的方法
  • Access-Control-Request-Headers:真正請(qǐng)求的自定義請(qǐng)求頭(若沒有自定義的就是空唄)

服務(wù)端在接收到此類請(qǐng)求后,就可以根據(jù)其值做邏輯決策啦。如果允許預(yù)檢請(qǐng)求通過(guò),返回個(gè)200即可,否則返回400或者403唄。

如果預(yù)檢成功,在響應(yīng)里應(yīng)該包含上文提到的響應(yīng)頭Access-Control-Allow-Origin和Access-Control-Expose-Headers,除此之外,服務(wù)端還可以做更精細(xì)化的控制,這些精細(xì)化控制的響應(yīng)頭為:

  • Access-Control-Allow-Methods:允許實(shí)際請(qǐng)求的Http方法(們)
  • Access-Control-Allow-Headers:允許實(shí)際請(qǐng)求的請(qǐng)求頭(們)
  • Access-Control-Max-Age:允許瀏覽器緩存此結(jié)果多久,單位:秒。有了緩存,以后就不用每次請(qǐng)求都發(fā)送預(yù)檢請(qǐng)求啦

❝說(shuō)明:以上響應(yīng)頭并不是必須的。若沒有此響應(yīng)頭,代表接受所有❞

預(yù)檢請(qǐng)求完成后,有個(gè)關(guān)鍵點(diǎn),便是瀏覽器拿到預(yù)檢請(qǐng)求的響應(yīng)后的處理邏輯,這里描述如下:

1.先通過(guò)自己的Origin匹配預(yù)檢響應(yīng)中的Access-Control-Allow-Origin的值,若不匹配就結(jié)束請(qǐng)求,若匹配就繼續(xù)下一步驗(yàn)證

  • 關(guān)于Access-Control-Allow-Origin的驗(yàn)證邏輯,請(qǐng)參考文上描述

2.拿到預(yù)檢響應(yīng)中的Access-Control-Allow-Methods頭。若此頭不存在,則進(jìn)行下一步,若存在則校驗(yàn)預(yù)檢請(qǐng)求頭Access-Control-Request-Method的值是否在此列表中,在其內(nèi)繼續(xù)下一步,否則失敗

3.拿到預(yù)檢響應(yīng)中的Access-Control-Request-Headers頭。同請(qǐng)求頭中的Access-Control-Allow-Headers值記性比較,全部包含在內(nèi)則匹配成功,否則失敗

以上全部匹配成功,就代表預(yù)檢成功,可以開始發(fā)送正式請(qǐng)求了。值得一提的事,Access-Control-Max-Age控制預(yù)檢結(jié)果的瀏覽器緩存,若緩存還生效的話,是不用單獨(dú)再發(fā)送OPTIONS請(qǐng)求的,匹配成功直接發(fā)送目標(biāo)真實(shí)即可。

Access-Control-Max-Age使用細(xì)節(jié)

Access-Control-Max-Age用于控制瀏覽器緩存預(yù)檢請(qǐng)求結(jié)果的時(shí)間,這里存在一些使用細(xì)節(jié)你需要注意:

1.若瀏覽器禁用了緩存,也就是勾選了Disable cache,那么此屬性無(wú)效。也就說(shuō)每次都還得發(fā)送OPTIONS請(qǐng)求

2.判斷此緩存結(jié)果的因素有兩個(gè):

  • 必須是同一URL(也就是Origin相同才會(huì)去找對(duì)應(yīng)的緩存)
  • header變化了,也會(huì)重新去發(fā)OPTIONS請(qǐng)求(當(dāng)然若去掉一些header編程簡(jiǎn)單請(qǐng)求了,就另當(dāng)別論嘍)

跨域請(qǐng)求代碼示例

正所謂說(shuō)再多,也抵不上跑幾個(gè)case,畢竟show me your code才是最重要。下面就針對(duì)跨域情況的簡(jiǎn)單請(qǐng)求、非簡(jiǎn)單請(qǐng)求(預(yù)檢通過(guò)、預(yù)檢不通過(guò))等case分別用代碼(基于文首代碼)說(shuō)明。

簡(jiǎn)單請(qǐng)求

簡(jiǎn)單請(qǐng)求正如其名,是最簡(jiǎn)單的請(qǐng)求方式。

  1. // 跨域請(qǐng)求 
  2. $.get("http://localhost:8080/cors"function (result) { 
  3.     $("#content").append(result).append("<br/>"); 
  4. }); 

服務(wù)端結(jié)果:

  1. INFO ...CorsServlet - 收到請(qǐng)求:/cors,方法:GET, Origin頭:http://localhost:63342 

瀏覽器結(jié)果:

若想讓請(qǐng)求正常,只需在服務(wù)端響應(yīng)頭里“加點(diǎn)料”就成:

  1. ... 
  2. resp.setHeader("Access-Control-Allow-Origin","http://localhost:63342"); 
  3. resp.getWriter().write("hello cors..."); 
  4. ... 

再次請(qǐng)求,結(jié)果成功:

對(duì)于簡(jiǎn)單請(qǐng)求來(lái)講,服務(wù)端只需要設(shè)置Access-Control-Allow-Origin這個(gè)一個(gè)頭即可,一個(gè)即可。

非簡(jiǎn)單請(qǐng)求

非簡(jiǎn)單請(qǐng)求的模擬非常簡(jiǎn)單,隨便打破一個(gè)簡(jiǎn)單請(qǐng)求的約束即可。比如我們先在上面get請(qǐng)求的基礎(chǔ)上自定義個(gè)請(qǐng)求頭:

  1. $.ajax({ 
  2.     type: "get"
  3.     url: "http://localhost:8080/cors"
  4.     headers: {secret:"kkjtjnbgjlfrfgv",token: "abc123"
  5. }); 

服務(wù)端代碼:

  1. /** 
  2.  * 在此處添加備注信息 
  3.  * 
  4.  * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a> 
  5.  * @site https://yourbatman.cn 
  6.  * @date 2021/6/9 10:36 
  7.  * @since 0.0.1 
  8.  */ 
  9. @Slf4j 
  10. @WebServlet(urlPatterns = "/cors"
  11. public class CorsServlet extends HttpServlet { 
  12.  
  13.     @Override 
  14.     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
  15.         String requestURI = req.getRequestURI(); 
  16.         String method = req.getMethod(); 
  17.         String originHeader = req.getHeader("Origin"); 
  18.  
  19.         log.info("收到請(qǐng)求:{},方法:{}, Origin頭:{}", requestURI, method, originHeader); 
  20.  
  21.         resp.setHeader("Access-Control-Allow-Origin","http://localhost:63342"); 
  22.         resp.setHeader("Access-Control-Expose-Headers","token,secret"); 
  23.         resp.setHeader("Access-Control-Allow-Headers","token,secret"); // 一般來(lái)講,讓此頭的值是上面那個(gè)的【子集】(或相同) 
  24.         resp.getWriter().write("hello cors..."); 
  25.     } 

點(diǎn)擊按鈕,瀏覽器發(fā)送請(qǐng)求,結(jié)果為:

服務(wù)端沒有任何日志輸出,也就是說(shuō)瀏覽器并未把實(shí)際請(qǐng)求發(fā)出去。什么原因?查看OPTIONS請(qǐng)求的返回一看便知:

根本原因?yàn)椋篛PTIONS的響應(yīng)頭里并未含有任何跨域相關(guān)信息,雖然預(yù)檢通過(guò)(注意:這個(gè)預(yù)檢是通過(guò)的喲,預(yù)檢不通過(guò)的場(chǎng)景就不用額外演示了吧~),但預(yù)檢的結(jié)果經(jīng)瀏覽器判斷此跨域?qū)嶋H請(qǐng)求不能發(fā)出,所以給攔下來(lái)了。

從代碼層面問(wèn)題就出現(xiàn)在resp.setHeader(xxx,xxx)放在了處理實(shí)際方法的Get方法上,顯然不對(duì)嘛,應(yīng)該放在doOptions()方法里才行:

  1. @Override 
  2. protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
  3.     super.doOptions(req, resp); 
  4.     resp.setHeader("Access-Control-Allow-Origin","http://localhost:63342"); 
  5.     resp.setHeader("Access-Control-Expose-Headers","token,secret"); 
  6.     resp.setHeader("Access-Control-Allow-Headers","token,secret"); // 一般來(lái)講,讓此頭的值是上面那個(gè)的【子集】(或相同) 

在此運(yùn)行,一切正常:

值得特別注意的是:設(shè)置跨域的響應(yīng)頭這塊代碼,在處理真實(shí)請(qǐng)求的doGet里也必須得有,否則服務(wù)端處理了,瀏覽器“不認(rèn)”也是會(huì)出跨域錯(cuò)誤的。

另外就是,Access-Control-Allow-Headers/Access-Control-Expose-Headers這兩個(gè)頭里必須包含你的請(qǐng)求的自定義的Header(標(biāo)準(zhǔn)的header不需要包含),否則依舊跨域失敗哦~

在實(shí)際生產(chǎn)場(chǎng)景中,Http請(qǐng)求的Content-type大都是application/json并非簡(jiǎn)單請(qǐng)求的頭,所以有個(gè)現(xiàn)實(shí)情況是:實(shí)際的跨域請(qǐng)求中,幾乎100%的情況下我們發(fā)的都是非簡(jiǎn)單請(qǐng)求。

Cros跨域使用展望

如上代碼示例,處理簡(jiǎn)單請(qǐng)求尚且簡(jiǎn)單,但對(duì)于非簡(jiǎn)單請(qǐng)求來(lái)說(shuō),我們?cè)赿oOptions和doGet都寫了一段setHeader的代碼,是否覺得麻煩呢?

另外,對(duì)于Access-Control-Allow-Origin若我需要允許多個(gè)源怎么辦呢?

❝Tips:Access-Control-Allow-Origin頭只允許一個(gè),且Access-Control-Allow-Origin:a.com,b.com依舊算作一個(gè)源的,它沒有逗號(hào)分隔的“特性”。從命名的藝術(shù)你也可看出,它并非是xxx-Origins而是xxx-Origin❞既然實(shí)際場(chǎng)景中幾乎100%都是非簡(jiǎn)單請(qǐng)求,那么對(duì)于控制非簡(jiǎn)單請(qǐng)求的Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Max-Age這些都都改如何賦值?是否有最佳實(shí)踐?

現(xiàn)在我們大都在Spring Framework/Spring Boot場(chǎng)景下開發(fā)應(yīng)用,框架層面是否提供一些優(yōu)雅的解決方案?

作為一名后端開發(fā)工程師(編程語(yǔ)言不限),也許你從未處理過(guò)跨域問(wèn)題,那么到底是誰(shuí)默默的幫你解決了這一切呢?是否想知其所以然?

總結(jié)

本文用很長(zhǎng)的篇幅介紹了Cors跨域資源共享的相關(guān)知識(shí),并且用代碼做了示范,希望能助你通關(guān)Cors這個(gè)狗皮膏藥一樣粘著我們的硬核知識(shí)點(diǎn)。本文文字?jǐn)⑹鲚^多,介紹了同源、跨域、Cors的幾乎所有概念,雖然略顯難啃,但這些是指導(dǎo)我們實(shí)踐的說(shuō)明書。

本文轉(zhuǎn)載自微信公眾號(hào)「 BAT的烏托邦」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 BAT的烏托邦公眾號(hào)。

 

責(zé)任編輯:姜華 來(lái)源: BAT的烏托邦
相關(guān)推薦

2019-04-10 10:32:16

CORSNginx反向代理

2022-04-29 09:11:14

CORS瀏覽器

2021-06-15 07:32:59

Cookie和Sess實(shí)現(xiàn)跨域

2014-08-19 10:36:02

AngularCORS

2011-01-24 13:12:01

AjaxDojojavascript

2011-09-06 09:56:24

JavaScript

2019-03-13 08:00:00

JavaScript作用域前端

2021-03-09 08:35:51

JSS作用域前端

2021-06-25 09:04:39

Cors跨域JSONP vs CO

2023-12-20 14:42:59

2024-01-31 07:55:52

2017-05-25 09:45:35

2020-08-31 19:20:33

瀏覽器CORS跨域

2013-11-27 10:23:23

2016-11-01 21:51:03

phpjavascript

2018-12-12 15:50:13

2023-11-20 08:02:49

2024-08-28 08:45:22

2021-06-17 07:15:36

Cors跨域多域名

2019-03-13 14:15:25

CORS跨域資源前端
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)