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

瀏覽器中的跨域問(wèn)題與 CORS

系統(tǒng) 瀏覽器
跨域,這或許是前端面試中最常碰到的問(wèn)題了,大概因?yàn)榭缬騿?wèn)題是瀏覽器環(huán)境中的特有問(wèn)題,而且隨處可見(jiàn),如同蚊子不僅盯你肉而且處處圍著你轉(zhuǎn)讓你心煩。

 [[340129]]

Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

什么是跨域?[1]

跨域,這或許是前端面試中最常碰到的問(wèn)題了,大概因?yàn)榭缬騿?wèn)題是瀏覽器環(huán)境中的特有問(wèn)題,而且隨處可見(jiàn),如同蚊子不僅盯你肉而且處處圍著你轉(zhuǎn)讓你心煩?!改憧矗诜?wù)器發(fā)起 HTTP 請(qǐng)求就不會(huì)有跨域問(wèn)題的」。

當(dāng)談到跨域問(wèn)題的解決方案時(shí),最流行也最簡(jiǎn)單的當(dāng)屬 CORS 了。

CORS

CORS 即跨域資源共享 (Cross-Origin Resource Sharing, CORS)。簡(jiǎn)而言之,就是在服務(wù)器端的響應(yīng)中加入幾個(gè)標(biāo)頭,使得瀏覽器能夠跨域訪問(wèn)資源。

這個(gè)響應(yīng)頭的字段設(shè)置就是 Access-Control-Allow-Origin: *以下是最簡(jiǎn)單的一個(gè) CORS 請(qǐng)求

  1. GET / HTTP/1.1 
  2. Host: shanyue.tech 
  3. Origin: http://shanyue.tech 
  4. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 
  5.  
  6. HTTP/1.1 200 OK 
  7. Access-Control-Allow-Origin: * 
  8. Content-Type: text/plain; charset=utf-8 
  9. Content-Length: 12 
  10. Date: Wed, 08 Jul 2020 17:03:44 GMT 
  11. Connection: keep-alive 

預(yù)請(qǐng)求與 Options

當(dāng)一個(gè)請(qǐng)求跨域且不是簡(jiǎn)單請(qǐng)求時(shí)就會(huì)發(fā)起預(yù)請(qǐng)求,也就是 Options。如果沒(méi)有預(yù)請(qǐng)求,萬(wàn)一有一個(gè)毀滅性的 POST 跨域請(qǐng)求直接執(zhí)行,雖然最后告知瀏覽器你沒(méi)有跨域權(quán)限,但是損失已造成,豈不虧大的。

以下條件構(gòu)成了簡(jiǎn)單請(qǐng)求:

  1. Method: 請(qǐng)求的方法是 GET、POST 及 HEAD
  2. Header: 請(qǐng)求頭是 Content-Type (有限制)、Accept-Language、Content-Language 等
  3. Content-Type: 請(qǐng)求類型是 application/x-www-form-urlencoded、multipart/form-data 或 text/plain非簡(jiǎn)單請(qǐng)求一般需要開(kāi)發(fā)者主動(dòng)構(gòu)造,在項(xiàng)目中常見(jiàn)的 Content-Type: application/json 及 Authorization: 為典型的「非簡(jiǎn)單請(qǐng)求」。與之有關(guān)的三個(gè)字段如下:
  • Access-Control-Allow-Methods: 請(qǐng)求所允許的方法, 「用于預(yù)請(qǐng)求 (preflight request) 中」
  • Access-Control-Allow-Headers: 請(qǐng)求所允許的頭,「用于預(yù)請(qǐng)求 (preflight request) 中」
  • Access-Control-Max-Age: 預(yù)請(qǐng)求的緩存時(shí)間

寫(xiě)一個(gè) CORS Middleware

既然 CORS 原理如此簡(jiǎn)單,那就拿起鍵盤(pán)寫(xiě)一個(gè)簡(jiǎn)單的 CORS 中間件吧,CORS 大致是設(shè)置幾個(gè)響應(yīng)頭吧

❝關(guān)于 cors 的響應(yīng)頭有哪些?[2]❞

「關(guān)于 CORS 的設(shè)置即是對(duì) CORS 相關(guān)響應(yīng)頭的設(shè)置,因此了解這些 headers 至關(guān)重要。無(wú)論對(duì)于配置的生產(chǎn)者和消費(fèi)者,及后端和前端而言,都應(yīng)該掌握!」

以下是關(guān)于 CORS 相關(guān)的 response headers 及其釋義

  • Access-Control-Allow-Origin: 可以把資源共享給那些域名,支持 * 及 特定域名
  • Access-Control-Allow-Credentials: 請(qǐng)求是否可以帶 cookie
  • Access-Control-Allow-Methods: 請(qǐng)求所允許的方法, 「用于預(yù)請(qǐng)求 (preflight request) 中」
  • Access-Control-Allow-Headers: 請(qǐng)求所允許的頭,「用于預(yù)請(qǐng)求 (preflight request) 中」
  • Access-Control-Expose-Headers: 那些頭可以在響應(yīng)中列出
  • Access-Control-Max-Age: 預(yù)請(qǐng)求的緩存時(shí)間

而關(guān)于 CORS 的中間件即是使用默認(rèn)值與配置來(lái)設(shè)置這些頭,如 koa/cors 需要傳遞以下參數(shù)。

  1. /** 
  2.  * CORS middleware 
  3.  * 
  4.  * @param {Object} [options] 
  5.  *  - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header 
  6.  *  - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE,PATCH' 
  7.  *  - {String|Array} exposeHeaders `Access-Control-Expose-Headers` 
  8.  *  - {String|Array} allowHeaders `Access-Control-Allow-Headers` 
  9.  *  - {String|Number} maxAge `Access-Control-Max-Age` in seconds 
  10.  *  - {Boolean|Function(ctx)} credentials `Access-Control-Allow-Credentials`, default is false
  11.  *  - {Boolean} keepHeadersOnError Add set headers to `err.header` if an error is thrown 
  12.  * @return {Function} cors middleware 
  13.  * @api public 
  14.  */ 
  15.  
  16. // Example 
  17. app.use(cors()) 

CORS 如何設(shè)置多域名

由上,貌似很簡(jiǎn)單,只需要服務(wù)端設(shè)置一下 Access-Control-Allow-Origin 就可以輕松解決問(wèn)題,但其中的坑有可能比你想象地要多很多!

先說(shuō)回 Access-Control-Allow-Origin,它所允許的值只有兩個(gè)

  • *: 所有域名
  • shanyue.tech: 特定域名

此時(shí),新問(wèn)題來(lái)了:

❝CORS 如果需要指定多個(gè)域名怎么辦[3]❞

「如果使用 Access-Control-Allow-Origin: *,則所有的請(qǐng)求不能夠攜帶cookie」,因此這種方案被擯棄。

因此這個(gè)問(wèn)題需要寫(xiě)代碼來(lái)解決,根據(jù)請(qǐng)求頭中的 Origin 來(lái)設(shè)置響應(yīng)頭 Access-Control-Allow-Origin

  1. 如果請(qǐng)求頭不帶有 Origin,證明未跨域,則不作任何處理
  2. 如果請(qǐng)求頭帶有 Origin,證明跨域,根據(jù) Origin 設(shè)置相應(yīng)的 Access-Control-Allow-Origin: // 獲取 Origin 請(qǐng)求頭const requestOrigin = ctx.get('Origin');// 如果沒(méi)有,則跳過(guò)if (!requestOrigin) { return await next();}// 設(shè)置響應(yīng)頭ctx.set('Access-Control-Allow-Origin', requestOrigin)

「但此時(shí)會(huì)出現(xiàn)一個(gè)新的問(wèn)題:緩存」

CORS 與 Vary: Origin

在討論與 Vary 關(guān)系時(shí),先拋出一個(gè)問(wèn)題:

❝如何避免 CDN 為 PC 端緩存移動(dòng)端頁(yè)面[4]❞

假設(shè)有兩個(gè)域名訪問(wèn) static.shanyue.tech 的跨域資源

  • foo.shanyue.tech,響應(yīng)頭中返回 Access-Control-Allow-Origin: foo.shanyue.tech
  • bar.shanyue.tech,響應(yīng)頭中返回 Access-Control-Allow-Origin: bar.shanyue.tech

看起來(lái)一切正常,但平靜的水面下波濤暗涌:

「如果 static.shanyue.tech 資源被 CDN 緩存,bar.shanyue.tech 再次訪問(wèn)資源時(shí),因緩存問(wèn)題,因此此時(shí)返回的是 Access-Control-Allow-Origin: foo.shanyue.tech,此時(shí)會(huì)有跨域問(wèn)題」

此時(shí),Vary: Origin 就上場(chǎng)了,代表為不同的 Origin 緩存不同的資源,這在各個(gè)服務(wù)器端 CORS 中間件也能體現(xiàn)出來(lái),如以下幾段代碼

此處是一段 koa 關(guān)于 CORS 的處理函數(shù): 詳見(jiàn) koajs/cors[5]

  1. return async function cors(ctx, next) { 
  2.   // If the Origin header is not present terminate this set of steps. 
  3.   // The request is outside the scope of this specification. 
  4.   const requestOrigin = ctx.get('Origin'); 
  5.  
  6.   // Always set Vary header 
  7.   // https://github.com/rs/cors/issues/10 
  8.   ctx.vary('Origin'); 

此處是一段 Go 語(yǔ)言關(guān)于 CORS 的處理函數(shù): 詳見(jiàn) rs/cors[6]

  1. func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { 
  2.  headers := w.Header() 
  3.  origin := r.Header.Get("Origin"
  4.  
  5.  // Always set Vary, see https://github.com/rs/cors/issues/10 
  6.   headers.Add("Vary""Origin"

進(jìn)一步改進(jìn)相關(guān)代碼:

  1. // 獲取 Origin 請(qǐng)求頭 
  2. const requestOrigin = ctx.get('Origin'); 
  3.  
  4. // 不管有沒(méi)有跨域都要設(shè)置 Vary: Origin 
  5. ctx.set('Vary''Origin'
  6.  
  7. // 如果沒(méi)有設(shè)置,說(shuō)明沒(méi)有跨域,跳過(guò) 
  8. if (!requestOrigin) { 
  9.   return await next(); 
  10.  
  11. // 設(shè)置響應(yīng)頭 
  12. ctx.set('Access-Control-Allow-Origin', requestOrigin) 

「那此時(shí)是不關(guān)于 CORS 的問(wèn)題就解決了?從中間件處理層面是這樣的,但仍然有一些服務(wù)端中間件使用問(wèn)題及瀏覽器問(wèn)題」

HSTS 與 CORS

HSTS (HTTP Strict Transport Security) 為了避免 HTTP 跳轉(zhuǎn)到 HTTPS 時(shí)遭受潛在的中間人攻擊,由瀏覽器本身控制到 HTTPS 的跳轉(zhuǎn)。如同 CORS 一樣,它也是有一個(gè)服務(wù)器的響應(yīng)頭來(lái)控制

  1. Strict-Transport-Security: max-age=5184000 

此時(shí)瀏覽器訪問(wèn)該域名時(shí),會(huì)使用 307 Internal Redirect,無(wú)需服務(wù)器干涉,自動(dòng)跳轉(zhuǎn)到 HTTPS 請(qǐng)求。

「如果前端訪問(wèn) HTTP 跨域請(qǐng)求,此時(shí)瀏覽器通過(guò) HSTS 跳轉(zhuǎn)到 HTTPS,但瀏覽器不會(huì)給出相應(yīng)的 CORS 響應(yīng)頭部,就會(huì)發(fā)生跨域問(wèn)題?!?/p>

  1. GET / HTTP/1.1 
  2. Host: shanyue.tech 
  3. Origin: http://shanyue.tech 
  4. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 
  5.  
  6. Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 

服務(wù)器異常處理與跨域異常

當(dāng)與其他中間件一起工作時(shí),也有可能出現(xiàn)問(wèn)題,由于不正確的執(zhí)行順序也可能導(dǎo)致跨域失敗。

假設(shè)有一個(gè)參數(shù)校驗(yàn)中間件,置于 CORS 中間件上方,由于校驗(yàn)失敗,并未穿過(guò) CORS 中間件,在前端會(huì)報(bào)錯(cuò)跨域失敗,真正的參數(shù)校驗(yàn)問(wèn)題掩蓋其中。

  1. const Koa = require('koa'
  2. const app = new Koa() 
  3. const cors = require('@koa/cors'
  4.  
  5. // 異常處理中間件 
  6. app.use(async (ctx, next) => { 
  7.   try { 
  8.     await next() 
  9.   } catch (e) { 
  10.     ctx.body = 'hello, error' 
  11.   } 
  12. }) 
  13.  
  14. // 某一個(gè)特定時(shí)刻肯定會(huì)報(bào)錯(cuò)的中間件 
  15. app.use(async (ctx, next) => { 
  16.   throw new Error('hello, world'
  17. }) 
  18.  
  19. // CORS 中間件 
  20. app.use(cors()) 
  21.  
  22. app.listen(3000) 

總結(jié)

本篇文章介紹了跨域問(wèn)題及其相應(yīng)的 CORS 解決方案,并列出了若干細(xì)節(jié)問(wèn)題。

CORS 通過(guò)服務(wù)器端設(shè)置若干響應(yīng)頭來(lái)正常工作

Access-Control-Allow-Origin: * 無(wú)法攜帶 Cookie,因此以此為多域名跨域設(shè)置有缺陷

服務(wù)器端通過(guò)響應(yīng)頭 Origin 來(lái)判斷是否為跨域請(qǐng)求,并以此設(shè)置多域名跨域,但要加上 Vary: Origin在編碼過(guò)程中要注意 HSTS 配置及服務(wù)器的中間件順序帶來(lái)的潛在風(fēng)險(xiǎn)

Reference

[1]什么是跨域?:

https://q.shanyue.tech/fe/js/216.html[2]關(guān)于 cors 的響應(yīng)頭有哪些?:

https://q.shanyue.tech/base/http/328.html[3]CORS 如果需要指定多個(gè)域名怎么辦:

https://q.shanyue.tech/base/http/364.html[4]如何避免 CDN 為 PC 端緩存移動(dòng)端頁(yè)面:

https://q.shanyue.tech/base/http/330.html[5]koajs/cors:

https://github.com/koajs/cors/blob/master/index.js#L54[6]rs/cors:

https://github.com/rs/cors/blob/be1c7e127af9fce006600894df5c5731d99cdc82/cors.go#L268

本文轉(zhuǎn)載自微信公眾號(hào)「全棧成長(zhǎng)之路」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系全棧成長(zhǎng)之路公眾號(hào)。

 

 

責(zé)任編輯:武曉燕 來(lái)源: 全棧成長(zhǎng)之路
相關(guān)推薦

2022-04-29 09:11:14

CORS瀏覽器

2019-04-10 10:32:16

CORSNginx反向代理

2015-04-24 10:37:40

Web安全瀏覽器跨域訪問(wèn)

2023-12-20 14:42:59

2023-12-12 09:45:16

前端瀏覽器

2021-06-15 07:32:59

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

2014-08-19 10:36:02

AngularCORS

2021-06-25 09:04:39

Cors跨域JSONP vs CO

2010-09-14 14:18:09

CSS跨瀏覽器開(kāi)發(fā)

2024-01-31 07:55:52

2024-08-23 09:00:18

開(kāi)發(fā)跨域請(qǐng)求

2010-05-31 10:11:02

2022-03-21 07:35:34

處理方式跨域

2022-08-17 14:04:18

COOPCOEP瀏覽器

2021-06-10 18:11:02

Cors跨域Web開(kāi)發(fā)Cors

2013-11-20 14:25:45

瀏覽器重繪

2011-07-15 09:56:54

NPAPI開(kāi)發(fā)火狐

2009-03-25 09:11:35

Firefox瀏覽器

2023-11-17 09:38:21

2010-04-05 21:57:14

Netscape瀏覽器
點(diǎn)贊
收藏

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