理解跨域首先必需要了解同源策略。同源策略是瀏覽器上爲安全性考慮實施的很是重要的安全策略。
何謂同源:
URL由協議、域名、端口和路徑組成,若是兩個URL的協議、域名和端口相同,則表示他們同源。
同源策略:
瀏覽器的同源策略,限制了來自不一樣源的"document"或腳本,對當前"document"讀取或設置某些屬性。 (白帽子講web安全[1])
從一個域上加載的腳本不容許訪問另一個域的文檔屬性。
舉個例子:
好比一個惡意網站的頁面經過iframe嵌入了銀行的登陸頁面(兩者不一樣源),若是沒有同源限制,惡意網頁上的javascript腳本就能夠在用戶登陸銀行的時候獲取用戶名和密碼。
在瀏覽器中,<script>、<img>、<iframe>、<link>等標籤均可以加載跨域資源,而不受同源限制,但瀏覽器限制了JavaScript的權限使其不能讀、寫加載的內容。
另外同源策略只對網頁的HTML文檔作了限制,對加載的其餘靜態資源如javascript、css、圖片等仍然認爲屬於同源。
代碼示例(http://localhost:8080/和http://localhost:8081因爲端口不一樣而不一樣源):javascript
http://localhost:8080/test.html <html> <head><title>test same origin policy</title></head> <body> <iframe id="test" src="http://localhost:8081/test2.html"></iframe> <script type="text/javascript"> document.getElementById("test").contentDocument.body.innerHTML = "write somthing"; </script> </body> </html> http://localhost:8081/test2.html <html> <head><title>test same origin policy</title></head> <body> Testing. </body> </html>
在Firefox中會獲得以下錯誤:
css
Error: Permission denied to access property 'body'
Document對象的domain屬性存放着裝載文檔的服務器的主機名,能夠設置它。
例如來自"blog.csdn.net"和來自"bbs.csdn.net"的頁面,都將document.domain設置爲"csdn.net",則來自兩個子域名的腳本便可相互訪問。
出於安全的考慮,不能設置爲其餘主domain,好比http://www.csdn.net/不能設置爲sina.com
html
Ajax (XMLHttpRequest)請求受到同源策略的限制。
Ajax經過XMLHttpRequest可以與遠程的服務器進行信息交互,另外XMLHttpRequest是一個純粹的Javascript對象,這樣的交互過程,是在後臺進行的,用戶不易察覺。
所以,XMLHTTP實際上已經突破了原有的Javascript的安全限制。
舉個例子:
假設某網站引用了其它站點的javascript,這個站點被compromise並在javascript中加入獲取用戶輸入並經過ajax提交給其餘站點,這樣就能夠源源不斷收集信息。
或者某網站由於存在漏洞致使XSS注入了javascript腳本,這個腳本就能夠經過ajax獲取用戶信息並經過ajax提交給其餘站點,這樣就能夠源源不斷收集信息。
若是咱們又想利用XMLHTTP的無刷新異步交互能力,又不肯意公然突破Javascript的安全策略,能夠選擇的方案就是給XMLHTTP加上嚴格的同源限制。
這樣的安全策略,很相似於Applet的安全策略。IFrame的限制還僅僅是不能訪問跨域HTMLDOM中的數據,而XMLHTTP則根本上限制了跨域請求的提交。(實際上下面提到了CORS已經放寬了限制)
隨着Ajax技術和網絡服務的發展,對跨域的要求也愈來愈強烈。下面介紹Ajax的跨域技術。
java
JSONP技術實際和Ajax沒有關係。咱們知道<script>標籤能夠加載跨域的javascript腳本,而且被加載的腳本和當前文檔屬於同一個域。所以在文檔中能夠調用/訪問腳本中的數據和函數。若是javascript腳本中的數據是動態生成的,那麼只要在文檔中動態建立<script>標籤就能夠實現和服務端的數據交互。
JSONP就是利用<script>標籤的跨域能力實現跨域數據的訪問,請求動態生成的JavaScript腳本同時帶一個callback函數名做爲參數。其中callback函數本地文檔的JavaScript函數,服務器端動態生成的腳本會產生數據,並在代碼中以產生的數據爲參數調用callback函數。當這段腳本加載到本地文檔時,callback函數就被調用。
第一個站點的測試頁面(http://localhost:8080/test.html): jquery
<script src="http://localhost:8081/test_data.js"> <script> function test_handler(data) { console.log(data); } </script>
服務器端的Javascript腳本(http://localhost:8081/test_data.js):
test_handler('{"data": "something"}');
爲了動態實現JSONP請求,可使用Javascript動態插入<script>標籤:web
<script type="text/javascript"> // this shows dynamic script insertion var script = document.createElement('script'); script.setAttribute('src', url); // load the script document.getElementsByTagName('head')[0].appendChild(script); </script>
JSONP協議封裝了上述步驟,jQuery中統一是如今AJAX中(其中data type爲JSONP):
ajax
http://localhost:8080/test?callback=test_handler
爲了支持JSONP協議,服務器端必須提供特別的支持[2],另外JSONP只支持GET請求。
api
使用代理方式跨域更加直接,由於SOP的限制是瀏覽器實現的。若是請求不是從瀏覽器發起的,就不存在跨域問題了。
使用本方法跨域步驟以下:
1. 把訪問其它域的請求替換爲本域的請求
2. 本域的請求是服務器端的動態腳本負責轉發實際的請求
各類服務器的Reverse Proxy功能均可以很是方便的實現請求的轉發,如Apache httpd + mod_proxy。
Eg.
爲了經過Ajax從http://localhost:8080訪問http://localhost:8081/api,能夠將請求發往http://localhost:8080/api。
而後利用Apache Web服務器的Reverse Proxy功能作以下配置:
ProxyPass /api http://localhost:8081/api
跨域
「Cross-origin resource sharing (CORS) is a mechanism that allows a web page to make XMLHttpRequests to another domain. Such "cross-domain" requests would otherwise be forbidden by web browsers, per the same origin security policy. CORS defines a way in which the browser and the server can interact to determine whether or not to allow the cross-origin request. It is more powerful than only allowing same-origin requests, but it is more secure than simply allowing all such cross-origin requests.」 ----Wikipedia[3]
經過在HTTP Header中加入擴展字段,服務器在相應網頁頭部加入字段表示容許訪問的domain和HTTP method,客戶端檢查本身的域是否在容許列表中,決定是否處理響應。
實現的基礎是JavaScript不可以操做HTTP Header。某些瀏覽器插件其實是具備這個能力的。
服務器端在HTTP的響應頭中加入(頁面層次的控制模式):
promise
Access-Control-Allow-Origin: example.com Access-Control-Request-Method: GET, POST Access-Control-Allow-Headers: Content-Type, Authorization, Accept, Range, Origin Access-Control-Expose-Headers: Content-Range Access-Control-Max-Age: 3600
多個域名之間用逗號分隔,表示對所示域名提供跨域訪問權限。"*"表示容許全部域名的跨域訪問。
客戶端能夠有兩種行爲:
1. 發送OPTIONS請求,請求Access-Control信息。若是本身的域名在容許的訪問列表中,則發送真正的請求,不然放棄請求發送。
2. 直接發送請求,而後檢查response的Access-Control信息,若是本身的域名在容許的訪問列表中,則讀取response body,不然放棄。
本質上服務端的response內容已經到達本地,JavaScript決定是否要去讀取。
Support: [Javascript Web Applications]
* IE >= 8 (須要安裝caveat)
* Firefox >= 3
* Safari 徹底支持
* Chrome 徹底支持
* Opera 不支持
測試頁面http://localhost:8080/test3.html使用jquery發送Ajax請求。
<html> <head><title>testing cross sop</title></head> <body> Testing. <script src="jquery-2.0.0.min.js"></script> <script type='text/javascript'> $.ajax({ url: 'http://localhost:8000/hello', success: function(data) { alert(data); }, error: function() { alert('error'); } }); </script> </body> </html>
測試Restful API(http://localhost:8000/hello/{name})使用bottle.py來host。
from bottle import route, run, response @route('/hello') def index(): return 'Hello World.' run(host='localhost', port=8000)
測試1:
測試正常的跨域請求的行爲。
測試結果:
1. 跨域GET請求已經發出,請求header中帶有
Origin http://localhost:8080
2. 服務器端正確給出response
3. Javascript拒絕讀取數據,在firebug中發現reponse爲空,而且觸發error回調
測試2:
測試支持CORS的服務器的跨域請求行爲。
對Restful API作以下改動,在response中加入header:
def index():
#Add CORS header#
response.set_header("Access-Control-Allow-Origin", "http://localhost:8080")
return 'Hello World.'
測試結果:
1. 跨域GET請求已經發出,請求header中帶有
Origin http://localhost:8080
2. 服務器端正確給出response
3. 客戶端正常獲取數據
測試3:
測試OPTIONS請求獲取CORS信息。
對客戶端的Ajax請求增長header:
$.ajax({ url: 'http://localhost:8000/hello', headers: {'Content-Type': 'text/html'}, success: function(data) { alert(data); }, error: function() { alert('error'); } });
對Restful API作以下改動:
@route('/hello', method = ['OPTIONS', 'GET'])
def index():
if request.method == 'OPTIONS':
return ''
return 'Hello World.'
測試結果:
1. Ajax函數會首先發送OPTIONS請求
2. 針對OPTIONS請求服務器
3. 客戶端發現沒有CORS header後不會發送GET請求
測試4:
增長服務器端對OPTIONS方法的處理。
對Restful API作以下改動:
@route('/hello', method = ['OPTIONS', 'GET'])
def index():
response.headers['Access-Control-Allow-Origin'] = 'http://localhost:8080'
response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type'
if request.method == 'OPTIONS':
return ''
return 'Hello World.'
測試結果:
1. Ajax函數會首先發送OPTIONS請求
2. 針對OPTIONS請求服務器
3. 客戶端匹配CORS header中的allow headers and orgin後會正確發送GET請求並獲取結果
測試發現,Access-Control-Allow-Headers是必須的。
CORS協議提高了Ajax的跨域能力,但也增長了風險。一旦網站被注入腳本或XSS攻擊,將很是方便的獲取用戶信息並悄悄傳遞出去。
Cookie中的同源只關注域名,忽略協議和端口。因此https://localhost:8080/和http://localhost:8081/的Cookie是共享的。
瀏覽器的各類插件也存在跨域需求。一般是經過在服務器配置crossdomain.xml[4],設置本服務容許哪些域名的跨域訪問。
客戶端會首先請求此文件,若是發現本身的域名在訪問列表裏,就發起真正的請求,不然不發送請求。
<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <allow-access-from domain="*"/> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy>
一般crossdomain.xml放置在網站根目錄。
互聯網的發展催生了跨域訪問的需求,各類跨域方法和協議知足了需求但也增長了各類風險。尤爲是XSS和CSRF等攻擊的盛行也得益於此。