跨域資源共享(Cross-Origin Resource Sharing)是一種機制,它使用額外的 HTTP 頭部告訴瀏覽器可讓一個web應用進行跨域資源請求。前端
若一個請求同時知足下述全部條件,則該請求可視爲「簡單請求」(注:灰色字體內容瞭解便可):ios
GET
HEAD
POST
Connection
、Accept-Encoding
等,可是設置無效)
Accept
Accept-Language
Content-Language
Content-Type
(值的範圍還要符合下面的要求)Content-Type
的值只能爲
application/x-www-form-urlencoded
multipart/form-data
text/plain
CORS 預檢請求發生在實際請求以前,用於檢查服務器是否支持 CORS,以判斷實際請求發送是否安全。預檢請求使用的方式是 OPTIONS
。git
當一個請求不是「簡單請求」時,即應該先發送預檢請求,好比:github
GET
、HEAD
、POST
X-xxx
Content-Type
值不是 application/x-www-form-urlencoded
、 multipart/form-data
、text/plain
,等等跨域請求,CORS要求服務端設置一些頭部字段,最重要的一個就是 Access-Control-Allow-Origin
。下面以案例進行說明,前端使用 axios 進行 http 傳輸,後端以 koa 做爲服務端框架,並使用CORS中間件 koa2-cors。web
// Client http://localhost:8080
simpleRequest() {
axios({
method: 'GET',
url: 'http://localhost:3000/api/simple'
}).then(data => {
console.log(data);
});
}
複製代碼
// Server http://localhost:3000
app.use(cors());
router.get('/api/simple', ctx => {
ctx.body = { result: 'simple request success' };
});
複製代碼
HTTP 報文:axios
HTTP 請求頭部有個 Origin
字段,表示請求來自哪裏。HTTP 響應頭部中的 Access-Control-Allow-Origin
表示哪一個域能夠訪問該資源。使用 Origin
和 Access-Control-Allow-Origin
就完成了最簡單的訪問控制。後端
// Client http://localhost:8080
mainRequest() {
axios({
method: 'POST',
url: 'http://localhost:3000/api/mainRequest',
headers: { 'X-test': 'CORS' } // 增長一個自定義的頭部字段,觸發預檢請求
}).then(data => {
console.log(data);
});
}
複製代碼
// Server http://localhost:3000
app.use(cors());
router.post('/api/mainRequest', ctx => {
ctx.body = { result: 'main request success' };
});
複製代碼
預檢請求的報文:api
請求首部字段 Access-Control-Request-Method
告知服務器,實際請求將使用 POST
方法。 請求首部字段 Access-Control-Request-Headers
告知服務器,實際請求將攜帶一個自定義請求首部字段:x-test。服務器據此決定,該實際請求是否被容許。跨域
響應首部字段 Access-Control-Allow-Methods
代表服務器容許客戶端使用哪些方法發起請求。 響應首部字段 Access-Control-Allow-Headers
代表服務器容許請求中攜帶字段 x-test。瀏覽器
實際請求的報文:
實際請求中發送了 X-test
頭部字段,響應狀態碼 200 OK。
能夠看到,預檢請求中 Client 和 Server 使用了更多的頭部字段來完成訪問控制。那麼,CORS 相關的請求頭部字段和響應頭部字段共有哪些呢?
Origin
頭部字段表示預檢請求或實際請求的源站。Access-Control-Request-Method
頭部字段用於預檢請求。其做用是,將實際請求所使用的 HTTP 方法告訴服務器。Access-Control-Request-Headers
頭部字段用於預檢請求。其做用是,將實際請求所攜帶的首部字段告訴服務器。注意,以上請求頭部字段無須手動設置,當使用 XMLHttpRequest
對象發起跨域請求時,它們已經被設置就緒。
Access-Control-Allow-Origin
其語法以下:
Access-Control-Allow-Origin: <origin> | * 複製代碼
origin 參數的值指定了容許訪問該資源的外域 URI。若是該字段的值爲通配符 *
,則表示容許來自全部域的請求。
注意,若是服務端指定了具體的域名而非 *
,那麼響應頭部中的 Vary
字段的值必須包含 Origin
。這將告訴客戶端:服務器對不一樣的源站返回不一樣的內容。
Access-Control-Allow-Methods
Access-Control-Allow-Methods
頭部字段用於預檢請求的響應。其指明瞭實際請求所容許使用的 HTTP 方法。
Access-Control-Allow-Headers
Access-Control-Allow-Headers
頭部字段用於預檢請求的響應。其指明瞭實際請求中容許攜帶的首部字段。
Access-Control-Expose-Headers
跨域請求中,瀏覽器默認狀況下經過API只能獲取到如下響應頭部字段:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
若是想要訪問其餘響應頭部信息,則須要在服務器端設置 Access-Control-Allow-Headers
。Access-Control-Expose-Headers
讓服務器把容許瀏覽器訪問的頭部字段放入白名單,好比:
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
複製代碼
這樣瀏覽器就可以訪問到 X-My-Custom-Header
和 X-Another-Custom-Header
響應頭部了。
Access-Control-Max-Age
Access-Control-Max-Age
字段指定了預檢請求的結果可以被緩存多久,單位是 秒,好比:
Access-Control-Max-Age: 5
複製代碼
表示在第一次預檢請求發出後,5s 內再訪問該接口時會直接發送實際請求,而不須要先發預檢請求。過了 5s 後,會再要求先發送預檢請求,以此類推。
app.use(
cors({
maxAge: 5
})
);
複製代碼
服務端設置了 5s 緩存,實際請求以下:
注意,若是設置緩存後,發現每次仍是會發送 OPTIONS 請求,請檢查你是否是勾選了「禁止緩存」。
Access-Control-Allow-Credentials
XMLHttpRequest.withCredentials
(或者 Request.credentials
)表示跨域請求中,user agent 是否應該發送 cookies、authorization headers 或者 TLS client certificates 等憑據。 Access-Control-Allow-Credentials
的做用就是:當 credentials 爲 「真」 時(XHR和Fetch設置方式不同),Access-Control-Allow-Credentials
告訴瀏覽器是否把響應內容暴露給前端 JS 代碼。好比:
// Client http://localhost:8080
simpleRequest() {
axios({
method: 'GET',
url: 'http://localhost:3000/api/simple',
withCredentials: true // 增長了withCredentials 選項
}).then(data => {
console.log(data);
});
}
// Server http://localhost:3000
app.use(
cors({
maxAge: 5,
// credentials: true
})
);
複製代碼
此時,服務端未設置 credentials: true
,發起請求能看到客戶端報錯:
若是服務端設置了 credentials: true
則客戶端就不會報錯了。
預檢請求的時候,Access-Control-Allow-Credentials
響應頭部字段表示實際請求中是否能夠使用 credentials。
關於 CORS 響應頭部字段的運用,建議看一下 koa2-cors 中間件的源碼。代碼只有幾十行,特別清晰易懂。
CORS 相關內容如上,瞭解以後能更好地幫助咱們解決平常聯調中出現的問題,好比:出現跨域了服務端怎麼設置,axios.post
方法發送一個對象時爲何會出現 OPTIONS 請求,代理服務器怎麼才能轉發cookies等等。