標(biāo)簽(空格分隔): Yii2
1 什么是CORS
CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。
它允許瀏覽器向跨源服務(wù)器發(fā)出XMLHttpRequest請(qǐng)求,從而繞過(guò)AJAX的同源策略(Same-origin Policy)
那么什么是Same-origin Policy呢?簡(jiǎn)單地說(shuō),在一個(gè)瀏覽器中訪問(wèn)的網(wǎng)站不能訪問(wèn)另一個(gè)網(wǎng)站中的數(shù)據(jù),除非這兩個(gè)網(wǎng)站具有相同的Origin,也即是擁有相同的協(xié)議、主機(jī)地址以及端口。一旦這三項(xiàng)數(shù)據(jù)中有一項(xiàng)不同,那么該資源就將被認(rèn)為是從不同的Origin得來(lái)的,進(jìn)而不被允許訪問(wèn)。
2 CORS 請(qǐng)求分類
瀏覽器將CORS請(qǐng)求分成兩類:簡(jiǎn)單請(qǐng)求(simple request)和非簡(jiǎn)單請(qǐng)求(not-so-simple request)。
只要同時(shí)滿足以下兩大條件,就屬于簡(jiǎn)單請(qǐng)求。
1 請(qǐng)求方法是以下三種方法之一 :GET | POST | HEAD
2 HTTP 的頭信息不超出以下幾種字段:
Accept
Accept-Language
Content-Language
Content-Type (只限于三個(gè)值 application/x-www-form-urlencoded | multipart/form-data | text/plain )
Last-Event-ID
凡是不同時(shí)滿足上面兩個(gè)條件,就屬于非簡(jiǎn)單請(qǐng)求。
瀏覽器對(duì)這兩種請(qǐng)求的處理,是不一樣的。
3 準(zhǔn)備測(cè)試環(huán)境
3.1 新建 Stock Api
3.2 添加 Cors 過(guò)濾器
打開(kāi) 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 新建測(cè)試站點(diǎn) 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 簡(jiǎn)單請(qǐng)求
對(duì)于簡(jiǎn)單請(qǐng)求,瀏覽器直接發(fā)出CORS請(qǐng)求。具體來(lái)說(shuō),就是在頭信息之中,增加一個(gè)Origin字段。
4.1 同源請(qǐng)求
修改 index.html,設(shè)置 x = 0 ,刷新瀏覽器

4.2 請(qǐng)求被拒絕
刷新瀏覽器,執(zhí)行 GET 請(qǐng)求,發(fā)現(xiàn)請(qǐng)求頭被自動(dòng)添加了 Origin 字段。
Origin字段用來(lái)說(shuō)明,本次請(qǐng)求來(lái)自哪個(gè)源(協(xié)議'+ 域名 + 端口)。服務(wù)器根據(jù)這個(gè)值,決定是否同意這次請(qǐng)求。



對(duì)比同源請(qǐng)求,CORS請(qǐng)求多了一個(gè) Origin 請(qǐng)求頭,其他沒(méi)有多大區(qū)別。Origin包含協(xié)議名、地址以及一個(gè)可選的端口,她是瀏覽器自動(dòng)添加的,無(wú)法手動(dòng)添加。
請(qǐng)求被拒絕的情況下,CORS 響應(yīng)頭和普通請(qǐng)求響應(yīng)頭沒(méi)有區(qū)別。
4.3 請(qǐng)求被允許
修改 StockController,添加允許的請(qǐng)求方法
......
'Access-Control-Request-Method' => ['GET'],
......
刷新瀏覽器,重新訪問(wèn) GET 請(qǐng)求



請(qǐng)求成功,響應(yīng)頭中會(huì)包含一些 以“Access-Control-”作為前綴的項(xiàng)目。
可能的響應(yīng)字段有:
Access-Control-Allow-Origin(必須)
該字段是必須的。它的值要么是請(qǐng)求時(shí)Origin字段的值,要么是一個(gè)*,表示接受任意域名的請(qǐng)求。Access-Control-Allow-Credentials(可選)
它的值是一個(gè)布爾值,表示是否允許發(fā)送Cookie。默認(rèn)情況下,Cookie不包括在CORS請(qǐng)求之中。設(shè)為true,即表示服務(wù)器明確許可,Cookie可以包含在請(qǐng)求中,一起發(fā)給服務(wù)器。這個(gè)值也只能設(shè)為true,如果服務(wù)器不要瀏覽器發(fā)送Cookie,刪除該字段即可。Access-Control-Expose-Headers(可選)
CORS請(qǐng)求時(shí),XMLHttpRequest對(duì)象的getResponseHeader()方法只能拿到6個(gè)基本字段:
Cache-Control | Content-Language | Content-Type | Expires | Last-Modified | Pragma
如果想拿到其他字段,就必須在Access-Control-Expose-Headers里面指定,并以逗號(hào)進(jìn)行分隔。
4.3 結(jié)論
4.3.1 不在許可范圍內(nèi)
如果 Origin 指定的源,不在許可范圍內(nèi),服務(wù)器會(huì)返回一個(gè)正常的HTTP回應(yīng)。瀏覽器發(fā)現(xiàn),這個(gè)回應(yīng)的頭信息沒(méi)有包含Access-Control-Allow-Origin字段(詳見(jiàn)下文),就知道出錯(cuò)了,從而拋出一個(gè)錯(cuò)誤,被XMLHttpRequest的onerror回調(diào)函數(shù)捕獲。注意,這種錯(cuò)誤無(wú)法通過(guò)狀態(tài)碼識(shí)別,因?yàn)镠TTP回應(yīng)的狀態(tài)碼有可能是200。
4.3.2 在許可范圍內(nèi)
如果Origin指定的域名在許可范圍內(nèi),服務(wù)器返回的響應(yīng),會(huì)多出幾個(gè)頭信息字段。
5 非簡(jiǎn)單請(qǐng)求
5.1 預(yù)檢請(qǐng)求
非簡(jiǎn)單請(qǐng)求是那種對(duì)服務(wù)器有特殊要求的請(qǐng)求,比如請(qǐng)求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。
非簡(jiǎn)單請(qǐng)求的CORS請(qǐng)求,會(huì)在正式通信之前,增加一次HTTP查詢請(qǐng)求,稱為"預(yù)檢"請(qǐng)求(preflight)。
瀏覽器先詢問(wèn)服務(wù)器,當(dāng)前網(wǎng)頁(yè)所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動(dòng)詞和頭信息字段。只有得到肯定答復(fù),瀏覽器才會(huì)發(fā)出正式的XMLHttpRequest請(qǐng)求,否則就報(bào)錯(cuò)。
設(shè)置 js 變量 x = 3,刷新瀏覽器



可以看到只發(fā)送了一個(gè) OPTIONS 請(qǐng)求,沒(méi)有發(fā)起 PUT 請(qǐng)求,原因是響應(yīng)頭 Access-Control-Allow-Methods 沒(méi)有包含 PUT 方法。
預(yù)檢請(qǐng)求以 OPTIONS 方法發(fā)送,請(qǐng)求中包含:
Origin: 表示請(qǐng)求來(lái)自哪個(gè)源。
Access-Control-Request-Method – 該項(xiàng)內(nèi)容是實(shí)際請(qǐng)求的種類,可以是GET、POST之類的簡(jiǎn)單請(qǐng)求,也可以是PUT、DELETE等等。
Access-Control-Request-Headers – 該項(xiàng)是一個(gè)以逗號(hào)分隔的列表,當(dāng)中是復(fù)雜請(qǐng)求所使用的頭部。
預(yù)檢請(qǐng)求可能的響應(yīng)頭:
Access-Control-Allow-Origin(必含)
和簡(jiǎn)單請(qǐng)求一樣的,必須包含一個(gè)域。Access-Control-Allow-Methods(必含)
這是對(duì)預(yù)請(qǐng)求當(dāng)中Access-Control-Request-Method的回復(fù),這一回復(fù)將是一個(gè)以逗號(hào)分隔的列表。盡管客戶端或許只請(qǐng)求某一方法,但服務(wù)端仍然可以返回所有允許的方法,以便客戶端將其緩存。Access-Control-Allow-Headers(當(dāng)預(yù)請(qǐng)求中包含Access-Control-Request-Headers時(shí)必須包含)
這是對(duì)預(yù)請(qǐng)求當(dāng)中Access-Control-Request-Headers的回復(fù),和上面一樣是以逗號(hào)分隔的列表,可以返回所有支持的頭部。Access-Control-Allow-Credentials(可選)
和簡(jiǎn)單請(qǐng)求當(dāng)中作用相同。Access-Control-Max-Age(可選)
以秒為單位的緩存時(shí)間。預(yù)請(qǐng)求的的發(fā)送并非免費(fèi)午餐,允許時(shí)應(yīng)當(dāng)盡可能緩存。
5.2 預(yù)檢請(qǐng)求的響應(yīng)
5.2.1 修改 StockController,在響應(yīng)頭 Access-Control-Allow-Methods: 包含 PUT 方法
'Access-Control-Request-Method' => ['GET','PUT'],
5.2.2 刷新瀏覽器,重新發(fā)起 PUT 請(qǐng)求
可以看到瀏覽器先執(zhí)行 OPTIONS 請(qǐng)求,然后再執(zhí)行 PUT 請(qǐng)求,兩個(gè)請(qǐng)求都成功了,原因是 OPTIONS 請(qǐng)求的響應(yīng)頭 Access-Control-Allow-Methods 中包含 PUT 方法。



5.3 瀏覽器的正常請(qǐng)求和響應(yīng)
一旦服務(wù)器通過(guò)了"預(yù)檢"請(qǐng)求,以后每次瀏覽器正常的CORS請(qǐng)求,就都跟簡(jiǎn)單請(qǐng)求一樣,會(huì)有一個(gè)Origin頭信息字段。服務(wù)器的回應(yīng),也都會(huì)有一個(gè)Access-Control-Allow-Origin頭信息字段。
PUT 請(qǐng)求


6 參考
跨域資源共享 CORS 詳解
HTTP access control (CORS)
CORS——跨域請(qǐng)求那些事兒
利用CORS實(shí)現(xiàn)跨域請(qǐng)求