古語雲:“無規矩不成方圓”。同源政策(
Same origin policy
)是一種約定,它是浏覽器最核心也最基本的安全功能,如果缺少了同源政策,則浏覽器的正常功能可能都會受到影響。可以說Web是建構在同源政策基礎之上的,浏覽器隻是針對同源政策的一種實作。
一、怎樣才算是同源
所謂同源是指域名(主機名或者IP位址)、端口、協定相同。不同的用戶端腳本(
JavaScript
、
ActionScript
)在沒明确授權的情況下,不能讀取對方的資源。
不同源的例子:
1. 域名(主機名或者IP位址)不同
與
http://news.company.com/index.html
不同源,域名不同,
http://www.company.com/index.html
子域與
news
子域不同。
www
與
http://company.com/index.html
不同源,域名不同,頂級域與
http://www.company.com/index.html
www
子域不是一個概念。
2. 端口不同
與
http://www.company.com:8080/index.html
不同源,端口不同,
http://www.company.com/index.html
與預設的
8080
80
端口不同。
3. 協定不同
與
https://www.company.com/index.html
不同源,協定不同,
http://www.company.com/index.html
與
https
http
是不同協定。
同源的例子:
與
http://www.company.com/a/c/index.html
屬于同源,域名,端口,協定均相同。
http://www.company.com/b/d/index.html
二、IE特例
在處理同源政策的問題上,IE存在兩個主要的不同之處。
1. 授權範圍(Trust Zones)
兩個互相之間高度互信的域名,如公司域名(corporate domains),不遵守同源政策的限制。
2. 端口
IE并沒有将端口号加入到同源政策的組成部分之中,是以,
與
https://www.company.com/index.html
屬于同源并且不受任何限制。
http://www.company.com/index.html
這些例外都是非标準的,其他也并未作出支援
三、讀寫權限
Web上的資源有很多,有的隻有讀權限,有的同時擁有讀和寫的權限。比如:HTTP請求頭裡的Referer(表示請求來源)隻可讀,同源和不同源就是根據這個Referer值進行判斷的, 而
document.cookie
則具備讀寫權限。這樣的區分也是為了安全上的考慮。
注意:中的同源隻關注域名,忽略協定和端口。是以
Cookie
和
https://localhost:8080/
的
http://localhost:8081/
是共享的。
Cookie
四、同源政策示例
如果是打開百度,在控制台中請求CSDN的網頁的話,會報下面的異常:
Chrome
中會報下面的異常:

IE
中會報下面的異常:
五、跨域通路資源
1. Ajax跨域( CORS
)
CORS
Ajax
主要是通過
XMLHttpRequest
對象與遠端的伺服器進行資訊互動的。但是
XMLHttpRequest
受到同源政策的限制,不能跨域通路資源。
如果
XMLHttpRequest
能夠跨域通路資源,則會導緻安全問題。因為
XMLHttpRequest
是一個純粹的
JavaScript
對象,如果某網站存在漏洞導緻
XSS
注入了
JavaScript
腳本,這個腳本就可以通過
Ajax
擷取使用者的資訊并通過
Ajax
送出到其他站點。
但是
XMLHttpRequest
可以通過通路目标域的伺服器,然後目标域的伺服器傳回的HTTP響應頭來授權是否允許跨域通路,假如目标站點
http://www.foo.com
傳回的響應頭如下:
Access-Control-Allow-Origin: http://www.evil.com
那麼
www.evil.com
站點上的用戶端腳本就有權通過
Ajax
技術對
www.foo.com
上的資料進行讀寫操作。
請求及響應過程如下:
通過在
HTTP Header
中加入擴充字段,伺服器在相應網頁頭部加入字段表示允許通路的
domain
和
HTTP method
,用戶端檢查自己的域是否在允許清單中,決定是否處理響應。
實作的基礎是
JavaScript
不能夠操作
HTTP Header
。某些浏覽器插件實際上是具有這個能力的。
伺服器端在
HTTP
的響應頭中加入(頁面層次的控制模式):
Access-Control-Allow-Origin: evil.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:
多個域名之間用逗号分隔,表示對所示域名提供跨域通路權限。”*”表示允許所有域名的跨域通路。
用戶端可以有兩種行為:
1. 發送
請求,請求
OPTIONS
Access-Control
資訊。如果自己的域名在允許的通路清單中,則發送真正的請求,否則放棄請求發送。
2. 直接發送請求,然後檢查
的
response
資訊,如果自己的域名在允許的通路清單中,則讀取
Access-Control
,否則放棄。
response body
本質上服務端的
response
内容已經到達本地,
JavaScript
決定是否要去讀取。
Support: [Javascript Web Applications]
* IE >= 8 (需要安裝caveat)
* Firefox >= 3
* Safari 完全支援
* Chrome 完全支援
* Opera 不支援
CORS
協定提升了
Ajax
的跨域能力,但也增加了風險。一旦網站被注入腳本或
XSS
攻擊,将非常友善的擷取使用者資訊并悄悄傳遞出去。
2. Jsonp實作跨域通路請求(單向跨域)
JSONP
(
JSON with Padding
)是一個簡單高效的跨域方式,
HTML
中的
script
标簽可以加載并執行其他域的
JavaScript
,于是我們可以通過
script
标記來動态加載其他域的資源。例如我要從域A的頁面
pageA
加載域B的資料,那麼在域B的頁面
pageB
中我以
JavaScript
的形式聲明
pageA
需要的資料,然後在
pageA
中用
script
标簽把
pageB
加載進來,那麼
pageB
中的腳本就會得以執行。
JSONP
在此基礎上加入了回調函數,
pageB
加載完之後會執行
pageA
中定義的函數,所需要的資料會以參數的形式傳遞給該函數。
第一個站點的測試頁面():
http://localhost:8080/test.html
伺服器端的<script src="http://localhost:8081/test_data.js" type="text/javascript"></script> <script> function test_handler(data) { console.log(data); } </script>
腳(
Javascript
):
http://localhost:8081/test_data.js
為了動态實作test_handler('{"data": "something"}');
請求,可以使用
JSONP
動态插入
Javascript
标簽:
<script>
<script type="text/javascript"> // this shows dynamic script insertion var script = document.createElement('script'); script.setAttribute('src', url); // load the script document.getElementsByTagName('head')[].appendChild(script); </script>
協定封裝了上述步驟,
JSONP
中統一實作在
jQuery
中(其中
Ajax
為
data type
):
JSONP
為了支援
http://localhost:8080/test?callback=test_handler
協定,伺服器端必須提供特别的支援,另外
JSONP
隻支援
JSONP
GET
請求。
利用
中的
jQuery
實作
Ajax
jsonp
跨域請求可以檢視我之前的部落格
JavaScript實作百度搜尋suggestion功能
3. document.domain(雙向跨域)
通過修改
document
的
domain
屬性,我們可以在域和子域或者不同的子域之間通信。同域政策認為域和子域隸屬于不同的域,比如
www.a.com
和
sub.a.com
是不同的域,這時,我們無法在
www.a.com
下的頁面中調用
sub.a.com
中定義的
JavaScrip
t方法。但是當我們把它們
document
的
domain
屬性都修改為
a.com
,浏覽器就會認為它們處于同一個域下,那麼我們就可以互相調用對方的
method
來通信了。
注意:浏覽器單獨儲存端口号。任何的指派操作,包括=
document.domain
都會以
document.domain
值覆寫掉原來的端口号。是以
null
頁面的腳本不能僅通過設定
company.com:8080
=
document.domain
就能與
"company.com"
通信。指派時必須帶上端口号,以確定端口号不會為
company.com
。另外,使用
null
來安全是讓子域通路其父域,需要同時将子域和父域的
document.domain
設定為相同的值。必須要這麼做,即使是簡單的将父域設定為其原來的值。沒有這麼做的話可能導緻授權錯誤。
document.domain
4. window.postMessage(雙向跨域)
window.postMessage
是
HTML5
定義的一個很新的方法,這個方法可以很友善地跨
window
通信。由于它是一個很新的方法,是以在很舊和比較舊的浏覽器中都無法使用。
例如:1targetWindow.postMessage(data, origin);
:要傳遞的資料,
.data
規範中提到該參數可以是
html5
的任意基本類型或可複制的對象,然而并不是所有浏覽器都做到了這點兒,部分浏覽器隻能處理字元串參數,是以我們在傳遞參數的時候需要使用
JavaScript
方法對對象參數序列化,在低版本IE中引用
JSON.stringify()
json2.js
可以實作類似效果。
2.
:字元串參數,指明目标視窗的源,協定+主機+端口号[+URL],
origin
會被忽略,是以可以不寫,這個參數是為了安全考慮,
URL
方法隻會将
postMessage()
message
傳遞給指定視窗,當然如果願意也可以建參數設定為”*”,這樣可以傳遞給任意視窗,如果要指定和目前視窗同源的話設定為”/”。
接收消息:
window.addEventListener('message', handler, false);
handler
的
event.data
是
postMessage
發送來的資料,
event.origin
是發送視窗的
origin
,
event.source
是發送消息的視窗引用
5. 跨域内嵌的資源
a.
<script src="..."></script>
标簽嵌入跨域腳本。文法錯誤資訊隻能在同源腳本中捕捉到
b.
标簽嵌入
<link rel="stylesheet" href="..." target="_blank" rel="external nofollow" >
CSS
。
c.
嵌入圖檔。支援的圖檔格式包括
<img>
,
PNG
,
JPEG
,
GIF
,
BMP
SVG
,…
d.
和
<video>
<audio>
嵌入多媒體資源。
e.
,
<object>
和
<embed>
<applet>
的插件。
f.
@font-face
引入的字型。一些浏覽器允許跨域字型( cross-origin fonts),一些需要同源字型(same-origin fonts)。
g.
和
<frame>
載入的任何資源。站點可以使用X-Frame-Options消息頭來阻止這種形式的跨域互動。
<iframe>
六、參考
1. 百度百科:同源政策
2. JavaScript 的同源政策
3. 浏覽器的同源政策
4. 跨域資源共享的10種方式
5. 同源政策和跨域通路