標簽(空格分隔): Yii2
1 什么是CORS
CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。
它允許瀏覽器向跨源服務器發(fā)出XMLHttpRequest請求,從而繞過AJAX的同源策略(Same-origin Policy)
那么什么是Same-origin Policy呢?簡單地說,在一個瀏覽器中訪問的網(wǎng)站不能訪問另一個網(wǎng)站中的數(shù)據(jù),除非這兩個網(wǎng)站具有相同的Origin,也即是擁有相同的協(xié)議、主機地址以及端口。一旦這三項數(shù)據(jù)中有一項不同,那么該資源就將被認為是從不同的Origin得來的,進而不被允許訪問。
2 CORS 請求分類
瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
只要同時滿足以下兩大條件,就屬于簡單請求。
1 請求方法是以下三種方法之一 :GET | POST | HEAD
2 HTTP 的頭信息不超出以下幾種字段:
Accept
Accept-Language
Content-Language
Content-Type (只限于三個值 application/x-www-form-urlencoded | multipart/form-data | text/plain )
Last-Event-ID
凡是不同時滿足上面兩個條件,就屬于非簡單請求。
瀏覽器對這兩種請求的處理,是不一樣的。
3 準備測試環(huán)境
3.1 新建 Stock Api
3.2 添加 Cors 過濾器
打開 StockController,修改代碼
<?php
namespace api\modules\v1\controllers;
use yii\filters\Cors;
use yii\helpers\ArrayHelper;
use yii\rest\ActiveController;
class StockController extends ActiveController
{
public $modelClass = 'api\models\Stock';
public function behaviors()
{
return ArrayHelper::merge([
[
'class' => Cors::className(),
'cors' => [
'Origin' => ['http://test.local'],
'Access-Control-Request-Method' => [],
'Access-Control-Request-Headers'=>['*']
],
],
], parent::behaviors());
}
3.3 新建測試站點 test.local
添加數(shù)據(jù)文件 data.json
{
"name": "ahcj"
}
添加文件 index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>CORS TEST</title>
</head>
<body id="body">
Please look at the Console and Network pannel ......
</body>
<script>
function getSameOrigin(){
var data = null;
var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("GET", "data.json");
xhr.send(data);
}
function get(){
var data = null;
var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("GET", "http://api.baojia.local/v1/stocks/3001");
xhr.send(data);
}
function post(){
var data = "symbol=999999&code=sh999999&name=This%20is%20a%20test&ipo_date=2017-070-7";
var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("POST", "http://api.baojia.local/v1/stocks");
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
xhr.send(data);
}
function put(){
var data = "symbol=999999&code=sh999999&name=This%20is%20an%20update%20test&ipo_date=2017-070-7";
var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("PUT", "http://api.baojia.local/v1/stocks/3001");
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
xhr.setRequestHeader("X-Custom-Header", "my-custom-header");
xhr.send(data);
}
var x = 0;
if( x === 0 ){
getSameOrigin()
}else if( x === 1){
get();
}else if( x === 2){
post();
}
else if( x === 3){
put();
}
</script>
</html>
4 簡單請求
對于簡單請求,瀏覽器直接發(fā)出CORS請求。具體來說,就是在頭信息之中,增加一個Origin字段。
4.1 同源請求
修改 index.html,設置 x = 0 ,刷新瀏覽器
4.2 請求被拒絕
刷新瀏覽器,執(zhí)行 GET 請求,發(fā)現(xiàn)請求頭被自動添加了 Origin 字段。
Origin字段用來說明,本次請求來自哪個源(協(xié)議'+ 域名 + 端口)。服務器根據(jù)這個值,決定是否同意這次請求。
對比同源請求,CORS請求多了一個 Origin 請求頭,其他沒有多大區(qū)別。Origin包含協(xié)議名、地址以及一個可選的端口,她是瀏覽器自動添加的,無法手動添加。
請求被拒絕的情況下,CORS 響應頭和普通請求響應頭沒有區(qū)別。
4.3 請求被允許
修改 StockController,添加允許的請求方法
......
'Access-Control-Request-Method' => ['GET'],
......
刷新瀏覽器,重新訪問 GET 請求
請求成功,響應頭中會包含一些 以“Access-Control-”作為前綴的項目。
可能的響應字段有:
Access-Control-Allow-Origin(必須)
該字段是必須的。它的值要么是請求時Origin字段的值,要么是一個*,表示接受任意域名的請求。Access-Control-Allow-Credentials(可選)
它的值是一個布爾值,表示是否允許發(fā)送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為true,即表示服務器明確許可,Cookie可以包含在請求中,一起發(fā)給服務器。這個值也只能設為true,如果服務器不要瀏覽器發(fā)送Cookie,刪除該字段即可。Access-Control-Expose-Headers(可選)
CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:
Cache-Control | Content-Language | Content-Type | Expires | Last-Modified | Pragma
如果想拿到其他字段,就必須在Access-Control-Expose-Headers里面指定,并以逗號進行分隔。
4.3 結(jié)論
4.3.1 不在許可范圍內(nèi)
如果 Origin 指定的源,不在許可范圍內(nèi),服務器會返回一個正常的HTTP回應。瀏覽器發(fā)現(xiàn),這個回應的頭信息沒有包含Access-Control-Allow-Origin字段(詳見下文),就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調(diào)函數(shù)捕獲。注意,這種錯誤無法通過狀態(tài)碼識別,因為HTTP回應的狀態(tài)碼有可能是200。
4.3.2 在許可范圍內(nèi)
如果Origin指定的域名在許可范圍內(nèi),服務器返回的響應,會多出幾個頭信息字段。
5 非簡單請求
5.1 預檢請求
非簡單請求是那種對服務器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。
非簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。
瀏覽器先詢問服務器,當前網(wǎng)頁所在的域名是否在服務器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答復,瀏覽器才會發(fā)出正式的XMLHttpRequest請求,否則就報錯。
設置 js 變量 x = 3,刷新瀏覽器
可以看到只發(fā)送了一個 OPTIONS 請求,沒有發(fā)起 PUT 請求,原因是響應頭 Access-Control-Allow-Methods 沒有包含 PUT 方法。
預檢請求以 OPTIONS 方法發(fā)送,請求中包含:
Origin: 表示請求來自哪個源。
Access-Control-Request-Method – 該項內(nèi)容是實際請求的種類,可以是GET、POST之類的簡單請求,也可以是PUT、DELETE等等。
Access-Control-Request-Headers – 該項是一個以逗號分隔的列表,當中是復雜請求所使用的頭部。
預檢請求可能的響應頭:
Access-Control-Allow-Origin(必含)
和簡單請求一樣的,必須包含一個域。Access-Control-Allow-Methods(必含)
這是對預請求當中Access-Control-Request-Method的回復,這一回復將是一個以逗號分隔的列表。盡管客戶端或許只請求某一方法,但服務端仍然可以返回所有允許的方法,以便客戶端將其緩存。Access-Control-Allow-Headers(當預請求中包含Access-Control-Request-Headers時必須包含)
這是對預請求當中Access-Control-Request-Headers的回復,和上面一樣是以逗號分隔的列表,可以返回所有支持的頭部。Access-Control-Allow-Credentials(可選)
和簡單請求當中作用相同。Access-Control-Max-Age(可選)
以秒為單位的緩存時間。預請求的的發(fā)送并非免費午餐,允許時應當盡可能緩存。
5.2 預檢請求的響應
5.2.1 修改 StockController,在響應頭 Access-Control-Allow-Methods: 包含 PUT 方法
'Access-Control-Request-Method' => ['GET','PUT'],
5.2.2 刷新瀏覽器,重新發(fā)起 PUT 請求
可以看到瀏覽器先執(zhí)行 OPTIONS 請求,然后再執(zhí)行 PUT 請求,兩個請求都成功了,原因是 OPTIONS 請求的響應頭 Access-Control-Allow-Methods 中包含 PUT 方法。
5.3 瀏覽器的正常請求和響應
一旦服務器通過了"預檢"請求,以后每次瀏覽器正常的CORS請求,就都跟簡單請求一樣,會有一個Origin頭信息字段。服務器的回應,也都會有一個Access-Control-Allow-Origin頭信息字段。
PUT 請求
6 參考
跨域資源共享 CORS 詳解
HTTP access control (CORS)
CORS——跨域請求那些事兒
利用CORS實現(xiàn)跨域請求