775
阿裏雲
跨域資源共享(CORS)__存儲空間管理_最佳實踐_對象存儲 OSS-阿裏雲
同源策略
跨域訪問,或者說 JavaScript 的跨域訪問問題,是瀏覽器出於安全考慮而設置的一個限製,即同源策略。舉例說明,當 A,B 兩個網站屬於不同的域時,如果來自於 A 網站的頁麵中的 JavaScript 代碼希望訪問 B 網站的時候,瀏覽器會拒絕該訪問。
然而,在實際應用中,經常會有跨域訪問的需求。比如用戶的網站 www.a.com,後端使用了 OSS,在網頁中提供了使用 JavaScript 實現的上傳功能,但是在該頁麵中,隻能向 www.a.com 發送請求,向其他網站發送的請求都會被瀏覽器拒絕。這樣會導致用戶上傳的數據必須從www.a.com 中轉。如果設置了跨域訪問的話,用戶就可以直接上傳到 OSS 而無需從 www.a.com 中轉。
CORS 介紹
跨域資源共享(Cross-Origin Resource Sharing,簡稱 CORS),是 HTML5 提供的標準跨域解決方案,具體的CORS規則可以參考 W3C CORS規範。
CORS 是一個由瀏覽器共同遵循的一套控製策略,通過HTTP的Header來進行交互。瀏覽器在識別到發起的請求是跨域請求的時候,會將Origin的Header加入HTTP請求發送給服務器,比如上麵那個例子,Origin的Header就是www.a.com。服務器端接收到這個請求之後,會根據一定的規則判斷是否允許該來源域的請求,如果允許的話,服務器在返回的響應中會附帶上Access-Control-Allow-Origin這個Header,內容為www.a.com來表示允許該次跨域訪問。如果服務器允許所有的跨域請求的話,將Access-Control-Allow-Origin的Header設置為*即可,瀏覽器根據是否返回了對應的Header來決定該跨域請求是否成功,如果沒有附加對應的Header,瀏覽器將會攔截該請求。
以上描述的僅僅是簡單情況,CORS規範將請求分為兩種類型,一種是簡單請求,另外一種是帶預檢的請求。預檢機製是一種保護機製,防止資源被本來沒有權限的請求修改。瀏覽器會在發送實際請求之前先發送一個OPTIONS的HTTP請求來判斷服務器是否能接受該跨域請求。如果不能接受的話,瀏覽器會直接阻止接下來實際請求的發生。
隻有同時滿足以下兩個條件才不需要發送預檢請求:
請求方法是如下之一:
- GET
- HEAD
- POST
所有的Header都在如下列表中:
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
預檢請求會附帶一些關於接下來的請求的信息給服務器,主要有以下幾種:
- Origin:請求的源域信息
- Access-Control-Request-Method:接下來的請求類型,如POST、GET等
- Access-Control-Request-Headers:接下來的請求中包含的用戶顯式設置的Header列表
服務器端收到請求之後,會根據附帶的信息來判斷是否允許該跨域請求,信息返回同樣是通過Header完成的:
- Access-Control-Allow-Origin:允許跨域的Origin列表
- Access-Control-Allow-Methods:允許跨域的方法列表
- Access-Control-Allow-Headers:允許跨域的Header列表
- Access-Control-Expose-Headers:允許暴露給JavaScript代碼的Header列表
- Access-Control-Max-Age:最大的瀏覽器緩存時間,單位s。
瀏覽器會根據以上的返回信息綜合判斷是否繼續發送實際請求。當然,如果沒有這些Header瀏覽器會直接阻止接下來的請求。
這裏需要再次強調的一點是,以上行為都是瀏覽器自動完成的,用戶無需關注這些細節。如果服務器配置正確,那麼和正常非跨域請求是一樣的。
CORS主要使用場景
CORS使用一定是在使用瀏覽器的情況下,因為控製訪問權限的是瀏覽器而非服務器。因此使用其它的客戶端的時候無需關心任何跨域問題。
使用CORS的主要應用就是在瀏覽器端使用Ajax直接訪問OSS的數據,而無需走用戶本身的應用服務器中轉。無論上傳或者下載。對於同時使用OSS和使用Ajax技術的網站來說,都建議使用CORS來實現與OSS的直接通信。
OSS對CORS的支持
OSS提供了CORS規則的配置從而根據需求允許或者拒絕相應的跨域請求。該規則是配置在Bucket級別的。詳情可以參考PutBucketCORS。
CORS請求的通過與否和OSS的身份驗證等是完全獨立的,即OSS的CORS規則僅僅是用來決定是否附加CORS相關的Header的一個規則。是否攔截該請求完全由瀏覽器決定。
使用跨域請求的時候需要關注瀏覽器是否開啟了Cache功能。當運行在同一個瀏覽器上分別來源於www.a.com和www.b.com的兩個頁麵都同時請求同一個跨域資源的時候,如果www.a.com的請求先到達服務器,服務器將資源帶上Access-Control-Allow-Origin的Header為www.a.com返回給用戶。這個時候www.b.com又發起了請求,瀏覽器會將Cache的上一次請求返回給用戶,這個時候Header的內容和CORS的要求不匹配,就會導致後麵的請求失敗。
目前OSS的所有Object相關的接口都提供了CORS相關的驗證,另外Multipart相關的接口目前也已經完全支持CORS驗證。
GET請求跨域示例
這裏舉一個使用Ajax從OSS獲取數據的例子。為了簡單起見,使用的Bucket都是public,訪問私有的Bucket的CORS配置是完全一樣的,隻需要在請求中附加簽名即可。
簡單開始
首先創建一個Bucket,這裏舉例為oss-cors-test,將權限設置為public-read,然後這裏創建一個test.txt的文本文檔,上傳到這個Bucket,現在控製台的顯示如下:
那麼test.txt的訪問地址是 https://oss-cors-test.oss-cn-hangzhou.aliyuncs.com/test.txt
請注意,請將下文中的地址換成自己試驗的地址
首先用curl直接訪問:
curl https://oss-cors-test.oss-cn-hangzhou.aliyuncs.com/test.txt
just for test
可見現在已經能正常訪問了。
那麼我們現在再試著使用Ajax技術來直接訪問這個網站。我們寫一個最簡單的訪問的HTML,可以將以下代碼複製到本地保存成html文件然後使用瀏覽器打開。因為沒有設置自定義的頭,因此這個請求是不需要預檢的。
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="./functions.js"></script>
</head>
<body>
<script type="text/javascript">
// Create the XHR object.
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// XHR for Chrome/Firefox/Opera/Safari.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// XDomainRequest for IE.
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// CORS not supported.
xhr = null;
}
return xhr;
}
// Make the actual CORS request.
function makeCorsRequest() {
// All HTML5 Rocks properties support CORS.
var url = 'https://oss-cors-test.oss-cn-hangzhou.aliyuncs.com/test.txt';
var xhr = createCORSRequest('GET', url);
if (!xhr) {
alert('CORS not supported');
return;
}
// Response handlers.
xhr.onload = function() {
var text = xhr.responseText;
var title = text;
alert('Response from CORS request to ' + url + ': ' + title);
};
xhr.onerror = function() {
alert('Woops, there was an error making the request.');
};
xhr.send();
}
</script>
<p align="center" style="font-size: 20px;">
<a href="#" onclick="makeCorsRequest(); return false;">Run Sample</a>
</p>
</body>
</html>
打開之後點擊鏈接(這裏以Chrome為例),當然,肯定會失敗:
利用Chrome的開發者工具來查看錯誤原因。
這裏告訴我們是因為沒有找到Access-Control-Allow-Origin這個頭。顯然,這就是因為服務器沒有配置CORS。
再進入Header界麵,可見瀏覽器發送了帶Origin的Request,因此是一個跨域請求,在Chrome上因為是本地文件所以Origin是null。
配置BucketCORS設置
知道了上文的問題,那麼現在可以設置Bucket相關的CORS來使上文的例子能夠執行成功。為了直觀起見,這裏使用控製台來完成CORS的設置。如果用戶的CORS設置不是很複雜,也建議使用控製台來完成CORS設置。CORS設置的界麵如下:
CORS設置是由一條一條規則組成的,真正匹配的時候會從第一條開始逐條匹配,以最早匹配上的規則為準。現在添加第一條規則,使用最寬鬆的配置:
這裏表示的意思就是所有的Origin都允許訪問,所有的請求類型都允許訪問,所有的Header都允許,最大的緩存時間為1s。
配置完成之後重新測試,結果如下:
可見現在已經可以正常訪問請求了。
因此排查問題來說,如果想要排除跨域帶來的訪問問題,可以將CORS設置為上麵這種配置。這種配置是允許所有的跨域請求的一種配置,因此如果這種配置下依然出錯,那麼就表明錯誤出現在其他的部分而不是CORS。
除了最寬鬆的配置之外,還可以配置更精細的控製機製來實現針對性的控製。比如對於這個場景可以使用如下最小的配置匹配成功:
因此對於大部分場景來說,用戶最好根據自己的使用場景來使用最小的配置以保證安全性。
利用跨域實現POST上傳
這裏提供一個更複雜的例子,這裏使用了帶簽名的POST請求,而且需要發送預檢請求。
將上麵的代碼下載下來之後,將以下對應的部分全部修改成自己對應的內容,然後使用自己的服務器運行起來。
這裏繼續使用上文oss-cors-test這個Bucket來進行測試,在測試之前先刪除所有的CORS相關的規則,恢複初始狀態。
訪問這個網頁,選擇一個文件上傳,結果如下:
同樣打開開發者工具,可以看到以下內容,根據上麵的Get的例子,很容易明白同樣發生了跨域的錯誤。不過這裏與Get請求的區別在於這是一個帶預檢的請求,所以可以從圖中看到是OPTIONS的Response沒有攜帶CORS相關的Header然後失敗了。
那麼我們根據對應的信息修改Bucket的CORS配置:
再重新運行,可見已經成功了,從控製台中也可以看到新上傳的文件。
測試一下內容:
$curl https://oss-cors-test.oss-cn-hangzhou.aliyuncs.com/events/1447312129218-test1.txt
post object test
CORS配置的一些注意事項
CORS配置一共有以下幾項:
- 來源:配置的時候要帶上完整的域信息,比如示例中
https://10.101.172.96:8001
.
注意不要遺漏了協議名如http,如果端口號不是默認的還要帶上端口號之類的。如果不確定的話,可以打開瀏覽器的調試功能查看Origin頭。這一項支持使用通配符*,但是隻支持一個,可以根據實際需要靈活配置。 - Method:按照需求開通對應的方法即可。
- Allow Header:允許通過的Header列表,因為這裏容易遺漏Header,因此建議沒有特殊需求的情況下設置為*。大小寫不敏感。
- Expose Header:暴露給瀏覽器的Header列表,不允許使用通配符。具體的配置需要根據應用的需求來選擇,隻暴露需要使用的Header,如ETag等。如果不需要暴露這些信息,這裏可以不填。如果有特殊需求可以單獨指定,大小寫不敏感。
- 緩存時間:如果沒有特殊情況可以酌情設置的大一點,比如60s。
因此,CORS的一般配置方法就是針對每個可能的訪問來源單獨配置規則,盡量不要將多個來源混在一個規則中,多個規則之間最好不要有覆蓋衝突。其他的權限隻開放需要的權限即可。
一些錯誤排查經驗
在調試類似程序的時候,很容易發生一種情況就是將其他錯誤和CORS錯誤弄混淆了。
舉個例子,當用戶因為簽名錯誤訪問被拒絕的時候,因為權限驗證發生在CORS驗證之前,因此返回的結果中並沒有帶上CORS的Header信息,有些瀏覽器就會直接報CORS出錯,但是實際服務器端的CORS配置是正確的。對於這種情況,我們有兩種方式:
查看HTTP請求的返回值,因為CORS驗證是一個獨立的驗證,並不影響主幹流程,因此像403之類的返回值不可能是由CORS導致的,需要先排除程序原因。如果之前發送了預檢請求,那麼可以查看預檢請求的結果,如果預檢請求中返回了正確的CORS相關的Header,那麼表示真實請求應該是被服務器端所允許的,因此錯誤隻可能出現在其他的方麵。
將服務器端的CORS設置按照如上文所示,改成允許所有來源所有類型的請求都通過的配置,再重新驗證。如果還是驗證失敗,表明有其他的錯誤發生。
最後更新:2016-11-23 16:04:09
上一篇:
CDN加速OSS__存儲空間管理_最佳實踐_對象存儲 OSS-阿裏雲
下一篇:
防盜鏈__存儲空間管理_最佳實踐_對象存儲 OSS-阿裏雲
雲數據庫Memcache版監控__雲服務監控_用戶指南_雲監控-阿裏雲
新增媒體__媒體接口_API使用手冊_視頻點播-阿裏雲
克隆實例__備份與恢複_用戶指南_雲數據庫 RDS 版-阿裏雲
獲取基本信息__獲取圖片信息_老版圖片服務手冊_對象存儲 OSS-阿裏雲
申請批量生成設備__接口列表_服務器端API_阿裏雲物聯網套件-阿裏雲
直播錄製__使用手冊_視頻直播-阿裏雲
超越穀歌雲平台,阿裏雲躋身全球前三,何時比肩AWS?
心髒病預測案例__案例_機器學習-阿裏雲
安裝方法__開發者工具_容器服務-阿裏雲
PostObject__關於Object操作_API 參考_對象存儲 OSS-阿裏雲
相關內容
常見錯誤說明__附錄_大數據計算服務-阿裏雲
發送短信接口__API使用手冊_短信服務-阿裏雲
接口文檔__Android_安全組件教程_移動安全-阿裏雲
運營商錯誤碼(聯通)__常見問題_短信服務-阿裏雲
設置短信模板__使用手冊_短信服務-阿裏雲
OSS 權限問題及排查__常見錯誤及排除_最佳實踐_對象存儲 OSS-阿裏雲
消息通知__操作指南_批量計算-阿裏雲
設備端快速接入(MQTT)__快速開始_阿裏雲物聯網套件-阿裏雲
查詢API調用流量數據__API管理相關接口_API_API 網關-阿裏雲
使用STS訪問__JavaScript-SDK_SDK 參考_對象存儲 OSS-阿裏雲