一次跨域問(wèn)題解析

什么是跨域

  • 請(qǐng)求協(xié)議(http,https)不同
  • 域名(domain)不同
  • 端口(port)不同

更詳細(xì)的說(shuō)明可以看下表:

URL1 URL2 說(shuō)明 是否允許通信
http://www.a.com/a.js http://www.a.com/b.js 同一域名下 允許
http://www.a.com/lab/a.js http://www.a.com/script/b.js 同一域名下不同文件夾 允許
=============以下都是跨域============
http://www.a.com:8000/a.js http://www.a.com/b.js 同一域名,不同端口 不允許
http://www.a.com/a.js https://www.a.com/b.js 同一域名畴栖,不同協(xié)議 不允許
http://www.a.com/a.js http://70.32.92.74/b.js 域名和域名對(duì)應(yīng)ip 不允許
http://www.a.com/a.js http://script.a.com/b.js 主域相同,子域不同 不允許
http://www.a.com/a.js http://a.com/b.js 同一域名艾岂,不同二級(jí)域名(同上) 不允許(cookie這種情況下也不允許訪問(wèn))
http://www.aa.com/a.js http://www.a.com/b.js 不同域名 不允許

準(zhǔn)備

前端網(wǎng)站地址:http://localhost:8080

服務(wù)端網(wǎng)址:http://localhost:59200

首先保證服務(wù)端是沒(méi)有處理跨域的枷邪,其次,先用postman測(cè)試服務(wù)端接口是正常的

當(dāng)網(wǎng)站8080去訪問(wèn)服務(wù)端接口時(shí),就產(chǎn)生了跨域問(wèn)題玖详,那么如何解決?

接下來(lái)我把跨域遇到的各種情況都列舉出來(lái)并通過(guò)nginx代理的方式來(lái)解決勤讽。

跨域主要涉及4個(gè)響應(yīng)頭:

  • Access-Control-Allow-Origin 用于設(shè)置允許跨域請(qǐng)求源地址 (預(yù)檢請(qǐng)求和正式請(qǐng)求在跨域時(shí)候都會(huì)驗(yàn)證)

  • Access-Control-Allow-Headers 跨域允許攜帶的特殊頭信息字段 (只在預(yù)檢請(qǐng)求驗(yàn)證)

  • Access-Control-Allow-Methods 跨域允許的請(qǐng)求方法或者說(shuō)HTTP動(dòng)詞 (只在預(yù)檢請(qǐng)求驗(yàn)證)

  • Access-Control-Allow-Credentials 是否允許跨域使用cookies蟋座,如果要跨域使用cookies,可以添加上此請(qǐng)求響應(yīng)頭脚牍,值設(shè)為true(設(shè)置或者不設(shè)置向臀,都不會(huì)影響請(qǐng)求發(fā)送,只會(huì)影響在跨域時(shí)候是否要攜帶cookies诸狭,但是如果設(shè)置券膀,預(yù)檢請(qǐng)求和正式請(qǐng)求都需要設(shè)置)。不過(guò)不建議跨域使用(項(xiàng)目中用到過(guò)驯遇,不過(guò)不穩(wěn)定芹彬,有些瀏覽器帶不過(guò)去),除非必要叉庐,因?yàn)橛泻芏喾桨缚梢源妗?/p>

網(wǎng)上很多文章都是告訴你直接Nginx添加這幾個(gè)響應(yīng)頭信息就能解決跨域舒帮,當(dāng)然大部分情況是能解決,但是我相信還是有很多情況,明明配置上了会前,也同樣會(huì)報(bào)跨域問(wèn)題好乐。

什么是預(yù)檢請(qǐng)求?

當(dāng)發(fā)生跨域條件時(shí)候瓦宜,覽器先詢問(wèn)服務(wù)器蔚万,當(dāng)前網(wǎng)頁(yè)所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動(dòng)詞和頭信息字段临庇。只有得到肯定答復(fù)反璃,瀏覽器才會(huì)發(fā)出正式的XMLHttpRequest請(qǐng)求,否則就報(bào)錯(cuò)假夺。

開(kāi)始動(dòng)手模擬:

Nginx代理端口:22222 ,配置如下

[[圖片上傳失敗...(image-f14974-1678155478063)]](javascript:void(0); "復(fù)制代碼")

server {
        listen 22222;
        server_name  localhost;
 location  / {
            proxy_pass  http://localhost:59200;
 }
}

測(cè)試代理是否成功淮蜈,通過(guò)Nginx代理端口2222再次訪問(wèn)接口,可以看到如下圖通過(guò)代理后接口也是能正常訪問(wèn)

接下來(lái)開(kāi)始用網(wǎng)站8080訪問(wèn)Nginx代理后的接口地址已卷,報(bào)錯(cuò)情況如下:

情況1:

通過(guò)錯(cuò)誤信息可以很清晰的定位到錯(cuò)誤(注意看標(biāo)紅部分)priflight說(shuō)明是個(gè)預(yù)請(qǐng)求梧田,CORS 機(jī)制跨域會(huì)首先進(jìn)行 preflight(一個(gè) OPTIONS 請(qǐng)求), 該請(qǐng)求成功后才會(huì)發(fā)送真正的請(qǐng)求侧蘸。 這一設(shè)計(jì)旨在確保服務(wù)器對(duì) CORS 標(biāo)準(zhǔn)知情裁眯,以保護(hù)不支持 CORS 的舊服務(wù)器

通過(guò)錯(cuò)誤信息,我們可以得到是預(yù)檢請(qǐng)求的請(qǐng)求響應(yīng)頭缺少了 Access-Control-Allow-Origin讳癌,錯(cuò)哪里穿稳,我們改哪里就好了。

修改Nginx配置信息如下:

server {
        listen       22222;
        server_name  localhost;
        location  / {
           #新增加部分
           add_header Access-Control-Allow-Origin 'http://localhost:8080';
           proxy_pass  http://localhost:59200; 
        }
    }

加完發(fā)現(xiàn)還有問(wèn)題

不過(guò)我們的配置沒(méi)什么問(wèn)題,問(wèn)題在Nginx,下圖鏈接http://nginx.org/en/docs/http/ngx_http_headers_module.html

add_header 指令用于添加返回頭字段晌坤,當(dāng)且僅當(dāng)狀態(tài)碼為圖中列出的那些時(shí)有效逢艘。如果想要每次響應(yīng)信息都攜帶頭字段信息,需要在最后添加always(經(jīng)我測(cè)試骤菠,只有Access-Control-Allow-Origin這個(gè)頭信息需要加always它改,其他的不加always也會(huì)攜帶回來(lái)),那我們加上試試

server {
        listen       22222;
        server_name  localhost;
        location  / {
            #新增加部分
           add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
           proxy_pass  http://localhost:59200; 
        }
    }

修改了配置后娩怎,發(fā)現(xiàn)生效了搔课,當(dāng)然不是跨域就解決了,是上面這個(gè)問(wèn)題已經(jīng)解決了截亦,因?yàn)閳?bào)錯(cuò)內(nèi)容已經(jīng)變了

情況2:

通過(guò)報(bào)錯(cuò)信息提示可以得知爬泥,是跨域?yàn)g覽器默認(rèn)行為的預(yù)請(qǐng)求(option請(qǐng)求)沒(méi)有收到ok狀態(tài)碼,此時(shí)再修改配置文件崩瓤,當(dāng)請(qǐng)求為option請(qǐng)求時(shí)候袍啡,給瀏覽器返回一個(gè)狀態(tài)碼(一般是204)

server {
        listen       22222;
        server_name  localhost;
        location  / {
           add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
           if ($request_method = 'OPTIONS') {
                return 204;
           }
           proxy_pass  http://localhost:59200; 
        }
    }

當(dāng)配置完后,發(fā)現(xiàn)報(bào)錯(cuò)信息變了

情況3:

意思就是預(yù)請(qǐng)求響應(yīng)頭Access-Control-Allow-Headers中缺少頭信息authorization(各種情況會(huì)不一樣却桶,在發(fā)生跨域后境输,在自定義添加的頭信息是不允許的蔗牡,需要添加到請(qǐng)求響應(yīng)頭Access-Control-Allow-Headers中,以便瀏覽器知道此頭信息的攜帶是服務(wù)器承認(rèn)合法的嗅剖,我這里攜帶的是authorization辩越,其他的可能是token之類(lèi)的,缺什么加什么)信粮,知道了問(wèn)題所在黔攒,然后修改配置文件,添加對(duì)應(yīng)缺少的部分强缘,再試試

server {
        listen       22222;
        server_name  localhost;
        location  / {
           add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
           if ($request_method = 'OPTIONS') {
               add_header Access-Control-Allow-Headers 'authorization'; #為什么寫(xiě)在if里面而不是接著Access-Control-Allow-Origin往下寫(xiě)督惰?因?yàn)檫@里只有預(yù)檢請(qǐng)求才會(huì)檢查
               return 204; 
          } 
        proxy_pass http://localhost:59200; 
 }

此時(shí)發(fā)現(xiàn)報(bào)錯(cuò)問(wèn)題又回到了情況1

經(jīng)測(cè)試驗(yàn)證,只要if ($request_method = 'OPTIONS') 里面寫(xiě)了 add_header 旅掂,當(dāng)為預(yù)檢請(qǐng)求時(shí)外部配置的都會(huì)失效

官方文檔是這樣說(shuō)的:

There could be several add_header directives. 
These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

意思就是當(dāng)前層級(jí)無(wú) add_header 指令時(shí)赏胚,則繼承上一層級(jí)的add_header。相反的若當(dāng)前層級(jí)有了add_header商虐,就應(yīng)該無(wú)法繼承上一層的add_header觉阅。

配置修改如下:

server {
        listen       22222;
        server_name  localhost;
        location  / {
            add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
            if ($request_method = 'OPTIONS') {
                add_header Access-Control-Allow-Origin 'http://localhost:8080';
                add_header Access-Control-Allow-Headers 'authorization';
                return 204;
            }
            proxy_pass  http://localhost:59200; 
        }
    }

此時(shí)改完發(fā)現(xiàn)跨域問(wèn)題已經(jīng)解決了,

不過(guò)以上雖然解決了跨域問(wèn)題秘车,但是考慮后期可能Nginx版本更新,不知道這個(gè)規(guī)則會(huì)不會(huì)被修改留拾,考慮到這樣的寫(xiě)法可能會(huì)攜帶上兩個(gè) Access-Control-Allow-Origin ,這種情況也是不允許的鲫尊,下面會(huì)說(shuō)到。所以配置適當(dāng)修改如下:

server {
        listen       22222;
        server_name  localhost;
        location  / {
            if ($request_method = 'OPTIONS') {
                add_header Access-Control-Allow-Origin 'http://localhost:8080';
                add_header Access-Control-Allow-Headers 'authorization';
                return 204;
            }
            if ($request_method != 'OPTIONS') {
                add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
            }
            proxy_pass  http://localhost:59200; 
        }
    }

情況4:

比較早期的API可能只用到了POST和GET請(qǐng)求沦偎,而Access-Control-Allow-Methods這個(gè)請(qǐng)求響應(yīng)頭跨域默認(rèn)只支持POST和GET疫向,當(dāng)出現(xiàn)其他請(qǐng)求類(lèi)型時(shí)候,同樣會(huì)出現(xiàn)跨域異常豪嚎。

比如搔驼,我這里將請(qǐng)求的API接口請(qǐng)求方式從原來(lái)的GET改成PUT,在發(fā)起一次試試侈询。在控制臺(tái)上會(huì)拋出錯(cuò)誤:

報(bào)錯(cuò)內(nèi)容也講的很清楚舌涨,在這個(gè)預(yù)請(qǐng)求中,PUT方法是不允許在跨域中使用的扔字,我們需要改下Access-Control-Allow-Methods的配置(缺什么加上么囊嘉,這里我只加了PUT,可以自己加全一點(diǎn))革为,讓瀏覽器知道服務(wù)端是允許的

server {
    listen 22222;
    server_name localhost;
    location / {
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080';
            add_header Access-Control-Allow-Headers 'content-type,authorization';
            add_header Access-Control-Allow-Methods 'PUT';#為這么只加在這個(gè)if中扭粱,不再下面的if也加上?因?yàn)檫@里只有預(yù)檢請(qǐng)求會(huì)校驗(yàn)震檩,當(dāng)然你加上也沒(méi)事琢蛤。
            return 204;
        }
        if ($request_method != 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
        }
        proxy_pass http://localhost:59200;
    }
}

這里注意一下蜓堕,改成PUT類(lèi)型后,Access-Control-Allow-Headers請(qǐng)求響應(yīng)頭又會(huì)自動(dòng)校驗(yàn)content-type這個(gè)請(qǐng)求頭博其,和情況3是一樣的套才,缺啥補(bǔ)啥就行了。如果不加上content-type慕淡,則會(huì)報(bào)如下錯(cuò)誤背伴。(想簡(jiǎn)單的話,Access-Control-Allow-Headers和Access-Control-Allow-Methods可以設(shè)置為 * ,表示全都匹配儡率。但是Access-Control-Allow-Origin就不建議設(shè)置成 * 了挂据,為了安全考慮,限制域名是很有必要的儿普。)

都加上后崎逃,問(wèn)題就解決了,這里報(bào)405是我服務(wù)端這個(gè)接口只開(kāi)放了GET眉孩,沒(méi)有開(kāi)放PUT个绍,而此刻我將此接口用PUT方法去請(qǐng)求,所以接口會(huì)返回這個(gè)狀態(tài)碼浪汪。

情況5:

最后再說(shuō)一種情況巴柿,就是后端處理了跨域,就不需要自己在處理了(這里吐槽下死遭,某些后端工程師自己改服務(wù)端代碼解決跨域广恢,但是又不理解其中原理,網(wǎng)上隨便找段代碼黏貼呀潭,導(dǎo)致響應(yīng)信息可能處理不完全钉迷,如method沒(méi)添加全,headers沒(méi)加到點(diǎn)上钠署,自己用的那個(gè)可能復(fù)制過(guò)來(lái)的并不包含實(shí)際項(xiàng)目所用到的糠聪,沒(méi)有添加options請(qǐng)求返回狀態(tài)碼等,導(dǎo)致Nginx再用通用的配置就會(huì)可能報(bào)以下異常)

意思就是此刻Access-Control-Allow-Origin請(qǐng)求響應(yīng)頭返回了多個(gè)谐鼎,而只允許有一個(gè)舰蟆,這種情況當(dāng)然修改配置去掉Access-Control-Allow-Origin這個(gè)配置就可以了,不過(guò)遇到這種情況狸棍,建議Nginx配置和服務(wù)端自己解決跨域只選其一身害。(這里注意如果按我上面的寫(xiě)法,if $request_method = 'OPTIONS' 這個(gè)里面的Access-Control-Allow-Origin可不能刪除隔缀,刪除!='OPTIONS'里面的就好了题造,因?yàn)檫@里如果是預(yù)檢請(qǐng)求直接就ruturn了,請(qǐng)求不會(huì)再轉(zhuǎn)發(fā)到59200服務(wù)猾瘸,如果也刪除了界赔,就會(huì)報(bào)和情況1一樣的錯(cuò)誤丢习。所以為什么說(shuō)要不服務(wù)端代碼層面解決跨域,要不就Nginx代理解決淮悼,不要混著搞咐低,不然不明白原理的人,網(wǎng)上找一段代碼貼就很可能解決不了問(wèn)題)

server {
        listen       22222;
        server_name  localhost;
        location  / {
            if ($request_method = 'OPTIONS') {
                add_header Access-Control-Allow-Origin 'http://localhost:8080';
                add_header Access-Control-Allow-Headers '*';
                add_header Access-Control-Allow-Methods '*';
                add_header Access-Control-Allow-Credentials 'true';
                return 204;
            }
            if ($request_method != 'OPTIONS') {
                add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
                add_header Access-Control-Allow-Credentials 'true';
            }
            proxy_pass  http://localhost:59200; 
        }
    }


server {
        listen       22222;
        server_name  localhost;
        location  / {
            add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
            add_header Access-Control-Allow-Headers '*';
            add_header Access-Control-Allow-Methods '*';
            add_header Access-Control-Allow-Credentials 'true';
            if ($request_method = 'OPTIONS') {
                return 204;
            }
            proxy_pass  http://localhost:59200; 
        }
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末袜腥,一起剝皮案震驚了整個(gè)濱河市见擦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羹令,老刑警劉巖鲤屡,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異福侈,居然都是意外死亡酒来,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)肪凛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)堰汉,“玉大人,你說(shuō)我怎么就攤上這事伟墙∏萄迹” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵戳葵,是天一觀的道長(zhǎng)就乓。 經(jīng)常有香客問(wèn)我,道長(zhǎng)拱烁,這世上最難降的妖魔是什么档址? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮邻梆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绎秒。我一直安慰自己浦妄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布见芹。 她就那樣靜靜地躺著剂娄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玄呛。 梳的紋絲不亂的頭發(fā)上阅懦,一...
    開(kāi)封第一講書(shū)人閱讀 52,262評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音徘铝,去河邊找鬼耳胎。 笑死惯吕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的怕午。 我是一名探鬼主播废登,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼郁惜!你這毒婦竟也來(lái)了堡距?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤兆蕉,失蹤者是張志新(化名)和其女友劉穎羽戒,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體虎韵,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡易稠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了劝术。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缩多。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖养晋,靈堂內(nèi)的尸體忽然破棺而出衬吆,到底是詐尸還是另有隱情,我是刑警寧澤绳泉,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布逊抡,位于F島的核電站,受9級(jí)特大地震影響零酪,放射性物質(zhì)發(fā)生泄漏冒嫡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一四苇、第九天 我趴在偏房一處隱蔽的房頂上張望孝凌。 院中可真熱鬧,春花似錦月腋、人聲如沸蟀架。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)片拍。三九已至,卻和暖如春妓肢,著一層夾襖步出監(jiān)牢的瞬間捌省,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工碉钠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纲缓,地道東北人卷拘。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像色徘,于是被迫代替她去往敵國(guó)和親恭金。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

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

  • 跨域失敗 當(dāng)使用jsonp跨域時(shí)褂策, 1:請(qǐng)求必須是GET 2:python 寫(xiě)的webservice返回的格式是J...
    旅行家John閱讀 444評(píng)論 0 1
  • 1. 什么是跨域横腿? 跨域一詞從字面意思看,就是跨域名嘛斤寂,但實(shí)際上跨域的范圍絕對(duì)不止那么狹隘耿焊。具體概念如下:只要協(xié)議...
    他在發(fā)呆閱讀 824評(píng)論 0 0
  • 同源策略 理解跨域首先必須要了解同源策略。同源策略是瀏覽器上為安全性考慮實(shí)施的非常重要的安全策略遍搞。何謂同源:URL...
    48892085f47c閱讀 724評(píng)論 0 6
  • 什么是跨域罗侯? 概念:指從一個(gè)域名的網(wǎng)頁(yè)向另一個(gè)網(wǎng)頁(yè)去請(qǐng)求資源,只要協(xié)議溪猿、域名钩杰、端口有任何一個(gè)不同,都被當(dāng)作是跨域诊县。...
    饑人谷_zhangfan閱讀 956評(píng)論 0 1
  • 一讲弄、關(guān)鍵詞 同源策略(same origin policy):是瀏覽器的一個(gè)安全功能,不同源的客戶端腳本在沒(méi)有明確...
    ForeverChance閱讀 6,761評(píng)論 0 18