<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>

          瀏覽器中的跨域問題與 CORS

          共 6520字,需瀏覽 14分鐘

           ·

          2020-09-05 13:54


          ?

          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]

          ?

          跨域,這或許是前端面試中最常碰到的問題了,大概因?yàn)榭缬騿栴}是瀏覽器環(huán)境中的特有問題,而且隨處可見,如同蚊子不僅盯你肉而且處處圍著你轉(zhuǎn)讓你心煩。「你看,在服務(wù)器發(fā)起 HTTP 請求就不會有跨域問題的」

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

          CORS

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

          這個(gè)響應(yīng)頭的字段設(shè)置就是?Access-Control-Allow-Origin: *

          以下是最簡單的一個(gè) CORS 請求

          GET / HTTP/1.1
          Host: shanyue.tech
          Origin: http://shanyue.tech
          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

          HTTP/1.1 200 OK
          Access-Control-Allow-Origin: *
          Content-Type: text/plain; charset=utf-8
          Content-Length: 12
          Date: Wed, 08 Jul 2020 17:03:44 GMT
          Connection: keep-alive

          預(yù)請求與 Options

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

          以下條件構(gòu)成了簡單請求:

          1. Method: 請求的方法是?GETPOST?及?HEAD
          2. Header: 請求頭是?Content-Type?(有限制)、Accept-LanguageContent-Language?等
          3. Content-Type: 請求類型是?application/x-www-form-urlencodedmultipart/form-data?或?text/plain

          非簡單請求一般需要開發(fā)者主動構(gòu)造,在項(xiàng)目中常見的?Content-Type: application/json?及?Authorization: ?為典型的「非簡單請求」。與之有關(guān)的三個(gè)字段如下:

          • Access-Control-Allow-Methods: 請求所允許的方法,?「用于預(yù)請求 (preflight request) 中」
          • Access-Control-Allow-Headers: 請求所允許的頭,「用于預(yù)請求 (preflight request) 中」
          • Access-Control-Max-Age: 預(yù)請求的緩存時(shí)間

          寫一個(gè) CORS Middleware

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

          ?

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

          ?

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

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

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

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

          /**
          ?*?CORS?middleware
          ?*
          ?*?@param?{Object}?[options]
          ?*??-?{String|Function(ctx)}?origin?`Access-Control-Allow-Origin`,?default?is?request?Origin?header
          ?*??-?{String|Array}?allowMethods?`Access-Control-Allow-Methods`,?default?is?'GET,HEAD,PUT,POST,DELETE,PATCH'
          ?*??-?{String|Array}?exposeHeaders?`Access-Control-Expose-Headers`
          ?*??-?{String|Array}?allowHeaders?`Access-Control-Allow-Headers`
          ?*??-?{String|Number}?maxAge?`Access-Control-Max-Age`?in?seconds
          ?*??-?{Boolean|Function(ctx)}?credentials?`Access-Control-Allow-Credentials`,?default?is?false.
          ?*??-?{Boolean}?keepHeadersOnError?Add?set?headers?to?`err.header`?if?an?error?is?thrown
          ?*?@return?{Function}?cors?middleware
          ?*?@api?public
          ?*/


          //?Example
          app.use(cors())

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

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

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

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

          此時(shí),新問題來了:

          ?

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

          ?

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

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

          1. 如果請求頭不帶有 Origin,證明未跨域,則不作任何處理
          2. 如果請求頭帶有 Origin,證明跨域,根據(jù) Origin 設(shè)置相應(yīng)的?Access-Control-Allow-Origin:
          //?獲取?Origin?請求頭
          const?requestOrigin?=?ctx.get('Origin');

          //?如果沒有,則跳過
          if?(!requestOrigin)?{
          ??return?await?next();
          }

          //?設(shè)置響應(yīng)頭
          ctx.set('Access-Control-Allow-Origin',?requestOrigin)

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

          CORS 與 Vary: Origin

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

          ?

          如何避免 CDN 為 PC 端緩存移動端頁面[4]

          ?

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

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

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

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

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

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

          return?async?function?cors(ctx,?next)?{
          ??//?If?the?Origin?header?is?not?present?terminate?this?set?of?steps.
          ??//?The?request?is?outside?the?scope?of?this?specification.
          ??const?requestOrigin?=?ctx.get('Origin');

          ??//?Always?set?Vary?header
          ??//?https://github.com/rs/cors/issues/10
          ??ctx.vary('Origin');
          }

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

          func?(c?*Cors)?handleActualRequest(w?http.ResponseWriter,?r?*http.Request)?{
          ?headers?:=?w.Header()
          ?origin?:=?r.Header.Get("Origin")

          ?//?Always?set?Vary,?see?https://github.com/rs/cors/issues/10
          ??headers.Add("Vary",?"Origin")
          }

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

          //?獲取?Origin?請求頭
          const?requestOrigin?=?ctx.get('Origin');

          //?不管有沒有跨域都要設(shè)置?Vary:?Origin
          ctx.set('Vary',?'Origin')

          //?如果沒有設(shè)置,說明沒有跨域,跳過
          if?(!requestOrigin)?{
          ??return?await?next();
          }

          //?設(shè)置響應(yīng)頭
          ctx.set('Access-Control-Allow-Origin',?requestOrigin)

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

          HSTS 與 CORS

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

          Strict-Transport-Security: max-age=5184000

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

          「如果前端訪問 HTTP 跨域請求,此時(shí)瀏覽器通過 HSTS 跳轉(zhuǎn)到 HTTPS,但瀏覽器不會給出相應(yīng)的 CORS 響應(yīng)頭部,就會發(fā)生跨域問題。」

          GET / HTTP/1.1
          Host: shanyue.tech
          Origin: http://shanyue.tech
          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

          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)問題,由于不正確的執(zhí)行順序也可能導(dǎo)致跨域失敗。

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

          const?Koa?=?require('koa')
          const?app?=?new?Koa()
          const?cors?=?require('@koa/cors')

          //?異常處理中間件
          app.use(async?(ctx,?next)?=>?{
          ??try?{
          ????await?next()
          ??}?catch?(e)?{
          ????ctx.body?=?'hello,?error'
          ??}
          })

          //?某一個(gè)特定時(shí)刻肯定會報(bào)錯(cuò)的中間件
          app.use(async?(ctx,?next)?=>?{
          ??throw?new?Error('hello,?world')
          })

          //?CORS?中間件
          app.use(cors())

          app.listen(3000)

          總結(jié)

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

          1. CORS 通過服務(wù)器端設(shè)置若干響應(yīng)頭來正常工作
          2. Access-Control-Allow-Origin: *?無法攜帶 Cookie,因此以此為多域名跨域設(shè)置有缺陷
          3. 服務(wù)器端通過響應(yīng)頭?Origin?來判斷是否為跨域請求,并以此設(shè)置多域名跨域,但要加上?Vary: Origin
          4. 在編碼過程中要注意 HSTS 配置及服務(wù)器的中間件順序帶來的潛在風(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 端緩存移動端頁面: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



          掃碼關(guān)注公眾號,訂閱更多精彩內(nèi)容。


          給個(gè)[在看],是對達(dá)達(dá)最大的支持!
          瀏覽 27
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  成人电影久久久久久 | 国产人妻精品一区二区三水牛影视 | 成人黄网站在线观看 | 激情六月丁香 | 一级做A爱片久久毛片 |