完全讀懂前端跨域CORS

  前端小夥伴在使用AJAX的時候,相信對No Access-Control-Allow-Origin header這樣的報錯提示感到很頭疼,怎麼請求又跨域了。文章的開始,讓咱們從一個小故事開始。。。javascript

一個小故事

  在開發中,前端的童鞋們每次看到瀏覽器下面出現一長串紅色的跨域報錯就會很惱火,不停的唸叨着:那個誰誰誰,又沒有給我加跨域頭;後端小夥伴又會絕不示弱地反擊道:不就是Access-Control-Allow-Origin: *麼?已經有了啊!那爲何還會報錯?確定是你沒加好!前端

  因而,一場甩鍋大戰即將開始...java

shuaiguo

誰應該瞭解跨域

  說實話,每個先後端開發都應該要了解跨域的用法。ios

  前端的小夥伴可能會以爲跨域問題應該都是後端接口來處理的,可是若是多瞭解一些HTTP請求響應頭的,可以更快的定位問題,更快的解決接口異常,方便排查調試,因此但願可以耐下心把這篇文章看完。面試

何時會跨域

  在MDN中,對跨域是這麼解釋的:ajax

跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不一樣源服務器上的指定的資源。當一個資源從與該資源自己所在的服務器不一樣的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求express

  簡單來講就是當你向不一樣「域」的服務器發起網絡請求的時候,這個請求就跨域了。這裏不一樣「域」指的是不一樣的協議、域名、端口,有任何一個不一樣時,瀏覽器都視爲跨域。咱們在使用postman、fiddler等一些工具模擬發起http請求的時候,不會遇到跨域的狀況;當咱們在瀏覽器中請求不一樣域名的時候,雖然請求正常發出了,可是瀏覽器在請求返回時會進行一系列的校驗,判斷這次請求是否「合法」;若是不合法,返回結果就被瀏覽器攔截了。json

  咱們在進行POST或其餘跨域請求時,會發現只有一個OPTIONS請求,並無咱們想要的請求方法。axios

error.png

神祕的OPTIONS請求

  咱們沒有發送OPTIONS請求,那麼它是從哪裏來的呢?它的名稱叫CORS請求預檢,首先來看一下官方對它的定義是:後端

HTTP的OPTIONS方法用於獲取目的資源所支持的通訊選項。客戶端能夠對特定的URL使用OPTIONS方法,也能夠對整站(經過將 URL 設置爲「*」)使用該方法。

選項 是否容許 備註
Request has body No 沒有請求體
Successful response has body No 成功的響應有響應體
Safe Yes 安全
Idempotent Yes 密等性,不變性,同一個接口請求多少次都同樣
Cacheable No 不能緩存
Allowed in HTML forms No 不能在表單裏使用

  根據官網的文檔,咱們發現它沒有請求體,不能設置data,也不能直接發起OPTIONS請求。簡言之,OPTIONS請求是用於請求服務器對於某些接口等資源的支持狀況的,包括各類請求方法、頭部的支持狀況,僅做查詢使用。

  讓咱們詳細地看一下OPTIONS請求的真實面目吧,咱們首先構造一個POST請求:

var instance = axios.create({
    baseURL: 'http://192.168.0.100:8081'
})

instance({
    url: '/post',
    method: 'post',
    data:{
        url: 'xieyufei.com'
    }
})
複製代碼

options.png

  能夠看到OPTIONS請求頭很簡單,都沒有請求的body,有兩個字段Access-Control-Request-HeadersAccess-Control-Request-Method是新出現的,下面會說到這兩個字段的用法;那麼何時會觸發OPTIONS請求呢,這裏涉及到兩種CORS請求。

兩種請求

  瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request),簡單請求不會觸發CORS預檢請求。

簡單請求

  只要同時知足如下兩大條件,就屬於簡單請求:

  1. 請求方法是如下三種方法之一

    • HEAD
    • GET
    • POST
  2. HTTP的頭信息不超出如下幾種字段

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type 只限於三個值 application/x-www-form-urlencoded、multipart/form-data、text/plain
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Viewport-Width

  所以咱們只要把上面的請求加一個請求頭Content-Type,就能不觸發OPTIONS請求。

instance({
        url: '/post',
        method: 'post',
        headers:{
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        data:{
            url: 'xieyufei.com'
        }
    })
複製代碼

非簡單請求

  下面,咱們的重點來了,咱們在進行ajax請求時,通常都會在請求頭加一下自定義的數據,所以大多數請求都是非簡單請求。非簡單請求涉及如下幾個請求和響應的頭部的字段:

字段名 位置 用法 備註
Origin 請求頭 origin 代表預檢請求或實際請求的源站
Access-Control-Request-Method 請求頭 method 將實際請求所使用的 HTTP 方法告訴服務器。
Access-Control-Request-Headers 請求頭 field-name[, field-name]* 將實際請求所攜帶的頭部字段告訴服務器。
Access-Control-Allow-Origin 響應頭 origin or * 對於不須要攜帶身份憑證的請求,服務器能夠指定該字段的值爲通配符,表示容許來自全部域的請求
Access-Control-Allow-Methods 響應頭 method[, method]* 指明瞭實際請求所容許使用的 HTTP 方法。
Access-Control-Allow-Headers 響應頭 field-name[, field-name]* 指明瞭實際請求中容許攜帶的頭部字段。
Access-Control-Allow-Credentials 響應頭 true 指定了當瀏覽器的credentials設置爲true時是否容許瀏覽器讀取response的內容
Access-Control-Max-Age 響應頭 delta-seconds 指定了請求的結果可以被緩存多久

  在上面的OPTIONS請求中咱們能夠發現表格中的三個請求頭部都在該次請求中出現了,Access-Control-Request-MethodAccess-Control-Request-Headers用來詢問服務器,下面我要用POST方法和Content-Type頭部來請求,你就說你答不答應吧?

request.jpg

  在服務器端,咱們能夠這麼寫來容許請求跨域:

const express = require('express')
const app = express()
const PORT = 8081

let allowCrossDomain = function (req, res, next) {
    res.header('Access-Control-Allow-Origin', '*')
    res.header('Access-Control-Allow-Methods', 'POST')
    res.header('Access-Control-Allow-Headers', 'content-type')
    next()
}

app.use(allowCrossDomain)

app.post('/post', (req, res) => {
    res.json({
        msg: 'hi post'
    })
})

app.listen(PORT)
複製代碼

  這裏有咱們後端小夥伴很熟悉的Access-Control-Allow-Origin: *,用來代表全部的origin都容許跨域,至關於告訴瀏覽器:

allow.jpg

  這樣咱們就能看到咱們期待已久的POST請求,同時返回的頭部信息中帶上了CORS的響應頭;同時咱們能夠看到axios默認的Content-Typeapplication/json;charset=UTF-8,不在僅限的三個值中,所以會觸發OPTIONS請求。

post.png

其餘頭部信息

  除了content-type,咱們還能夠在請求頭中添加一些本身定義的信息,好比須要傳給後臺的token之類的。

//瀏覽器端
instance({
    url: '/put',
    method: 'put',
    headers:{
        'X-Custom-Header': 'xieyufei-head'
    },
    data:{
        url: 'xieyufei.com'
    }
})

//服務器端
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'POST,PUT')
res.header('Access-Control-Allow-Headers', 'content-type, X-Custom-Header')
複製代碼

跨域獲取Cookie

  默認狀況下,Cookie是不包括在CORS的請求中,但有時候咱們又須要用到Cookie來傳輸數據,這時候咱們的Access-Control-Allow-Credentials字段就派上用處了,另外一方面,須要在AJAX請求中打開withCredentials屬性;咱們再把代碼進行以下改造:

//服務器端
res.header('Access-Control-Allow-Credentials', 'true')

//瀏覽器端
instance({
    url: '/put',
    method: 'put',
    //新增withCredentials
    withCredentials: true,
    headers:{
        'X-Custom-Header': 'xieyufei-head'
    },
    data:{
        url: 'xieyufei.com'
    }
})
複製代碼

  當咱們滿懷期待打開瀏覽器準備接收Cookie時,卻發現又報錯了:

credential-error.png

  通過對錯誤信息仔細閱讀,發現此次報錯跟上面的跨域報錯不徹底同樣,大概的意思是當請求的身份憑證包括的時候,Access-Control-Allow-Origin不能是通配符'*'(wildcard)。所以咱們大概瞭解到了錯誤的緣由是在通配符上面,咱們對代碼再進行一下改造:

const cookieParser = require('cookie-parser');  
app.use(cookieParser())

res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'POST')
res.header('Access-Control-Allow-Headers', 'content-type, X-Custom-Header')
res.header('Access-Control-Allow-Credentials', 'true')

app.post('/post', (req, res) => {
    console.log(req.cookies, 'cookie')
    res.json({
       msg: 'hi post'
    })
})
複製代碼

  這時候就能看到咱們想要的Cookie了。

總結

  CORS內容其實來講不是不少,也比較簡單,可是考驗動手實踐能力,面試時通常也會問到,所以經過express搭建服務器來加深對CORS知識的瞭解。

  更多文章請關注個人公衆號:前端壹讀。

wechat_qrcode.jpg
相關文章
相關標籤/搜索