同源與跨域(一)

參考:
瀏覽器的同源策略
瀏覽器同源政策及其規(guī)避方法
同源政策


什么是同源策略?

同源策略限制了從同一個(gè)源加載的文檔或腳本如何與來自另一個(gè)源的資源進(jìn)行交互愚战。這是一個(gè)用于隔離潛在惡意文件的重要安全機(jī)制胞枕。

源的定義

如果兩個(gè)頁面的協(xié)議羹与,端口(如果有指定)和域名都相同,則兩個(gè)頁面具有相同的源爹橱。

同源檢測示例:http://www.example.com/directory/page.html

http://www.example.com/directory/other.html                  // 成功
http://www.example.com/directory/inner/another.html          // 成功
https://example.com/index.html                               // 失敗     不同協(xié)議(http | https)
http://example.com:90/dir/secure.html                        // 失敗     不同端口(80 | 90)
http://new.example.com:90/dir/secure.html                    // 失敗     不同域名(new.example | example)

同源下的腳本只能讀取與所屬文檔同源的窗口和文檔的屬性萨螺。
同源政策的目的,是為了保護(hù)用戶信息的安全,防止惡意的網(wǎng)站竊取數(shù)據(jù)慰技。

目前椭盏,非同源將會受到限制的行為有:

  • 無法讀取非同源資源下的:Cookie、LocalStorage吻商、IndexedDB掏颊。
  • 無法操作非同源網(wǎng)頁的DOM。
  • 無法向非同源地址發(fā)送Ajax請求(實(shí)際上艾帐,服務(wù)器會收到請求乌叶,也會返回,但最終被瀏覽器攔截)柒爸。

注意

  • 對于當(dāng)前頁面來說頁面存放的 JS 文件的域不重要准浴,重要的是加載該 JS 頁面所在什么域。
  • 同源策略限制的是腳本嵌入的文本來源揍鸟,而不是腳本本身兄裂。

不受同源策略限制:

  • 頁面中的鏈接,重定向以及表單提交是不會受到同源策略限制的阳藻。
  • 跨域資源的引入是可以的。但是js不能讀寫加載的內(nèi)容谈撒。如嵌入到頁面中的<script src="..."></script>腥泥,<img>,<link>啃匿,<iframe>等蛔外。
實(shí)現(xiàn)一個(gè)同源限制

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" href="./HS/css/test.css">
</head>
<body>
    <h1>Hello World!</h1>
</body>
<script>

var xhr = new XMLHttpRequest();
xhr.open("GET","http://localhost:8080/getSomething",true);
xhr.send();

xhr.addEventListener("load",function() {
    console.log(xhr.responseText);
});

</script>
</html>

server.js

var http = require("http");
var fs = require("fs");
var path = require("path");
var url = require("url");

var server = http.createServer(function (req, res) {

    var pathObj = url.parse(req.url, true);
    console.log(pathObj.pathname);

    switch (pathObj.pathname) {
        case "/getSomething":
            res.end(
                JSON.stringify({ beijing: "sunny" })
            );
            break;

        default:
            fs.readFile(path.join(__dirname, pathObj.pathname), function (err, data) {
                if (err) {
                    res.writeHead(404, "not found");
                    res.end("<h1>404 Not Found</h1>")
                } else {
                    res.end(data);
                }
            })
            break;
    }
});

server.listen(8080);
console.log("visit http://localhost:8080/httpServer.html");

HTML頁面中的Ajax請求就是test point,頁面中溯乒,我們設(shè)置了一個(gè)Ajax請求夹厌,并在node server中的switch語句進(jìn)行設(shè)定,當(dāng)請求發(fā)送后裆悄,如果有一個(gè)指向http://localhost:8080/getSomething的請求,本地服務(wù)器就會返回一個(gè)被JSON.stringify()方法解析后的JS對象。否則事镣,就使用fs模塊讀取靜態(tài)文件(css驳规、js等)

正常訪問成功下的請求狀況:


每一個(gè)文件都顯示200 ok,請求成功艾君,并且服務(wù)器也收到了Ajax請求采够,并返回?cái)?shù)據(jù)。

我們稍微對Ajax請求URL更改一下:
http://www.localhost:8080/getSomething

運(yùn)行服務(wù)器:


此時(shí)冰垄,我們就實(shí)現(xiàn)了一個(gè)跨域請求蹬癌,只是沒有數(shù)據(jù)返回。

我們發(fā)現(xiàn),HTTP狀態(tài)碼顯示200逝薪,說明請求是成功的隅要,但卻沒有數(shù)據(jù)返回,且有紅字報(bào)錯(cuò)翼闽。

Failed to load http://www.localhost:8080/getSomething: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.
出現(xiàn)這段提示拾徙,就是瀏覽器告訴你,跨域了感局!

No 'Access-Control-Allow-Origin' header is present on the requested resource
意思是不存在Access-Control-Allow-Origin標(biāo)記尼啡,這個(gè)標(biāo)記是我們實(shí)現(xiàn)跨域時(shí)才會出現(xiàn)的,如果存在此標(biāo)記询微,瀏覽器不會對跨域的請求進(jìn)行攔截崖瞭,稍后會詳細(xì)解釋。

注意:請求是成功的撑毛,但是因?yàn)闉g覽器的安全機(jī)制书聚,請求的數(shù)據(jù)被攔截。


跨域 —— JSONP

JSONP是JSON with padding藻雌,填充式JSON或參數(shù)式JSON的縮寫雌续。是應(yīng)用JSON的一種新方法,在后來的Web服務(wù)器中非常流行胯杭。JSONP看起來與JSON差不多驯杜,只不過是被包含在函數(shù)調(diào)用中的JSON,就像下面這樣做个。

callback({"name": "Nicholas"});
JSONP由兩部分組成:
  • 回調(diào)函數(shù)
  • 數(shù)據(jù)

回調(diào)函數(shù)是當(dāng)響應(yīng)到來時(shí)應(yīng)該在頁面中調(diào)用的函數(shù)鸽心。回調(diào)函數(shù)的名字一般是在請求中指定的居暖。而數(shù)據(jù)就是傳入回調(diào)函數(shù)的JSON數(shù)據(jù)顽频。

http://freegeoip.net/json/?callback=handleResponse

這個(gè)URL請求在請求一個(gè)JSONP地理定位服務(wù)。通過查詢字符串來指定JSONP服務(wù)的回調(diào)函數(shù)是很常見的太闺,就像上面URL所示糯景,這里指定的回調(diào)函數(shù)名字叫handleResponse()

<script src="http://freegeoip.net/json/?callback=handleResponse"></script>

這個(gè)請求到達(dá)后端后跟束,后端回去解析callback這個(gè)參數(shù)獲取到字符串handleResponse莺奸,在發(fā)送數(shù)據(jù)做以下處理:

假設(shè)之前后端返回?cái)?shù)據(jù): {"city": "hangzhou", "weather": "晴天"} ,現(xiàn)在后端返回?cái)?shù)據(jù): handleResponse({"city": "hangzhou", "weather": "晴天"}) 前端script標(biāo)簽在加載數(shù)據(jù)后會把 handleResponse({"city": "hangzhou", "weather": "晴天"})做為 js 來執(zhí)行冀宴,這實(shí)際上就是調(diào)用handleResponse這個(gè)函數(shù)灭贷,同時(shí)參數(shù)是{"city": "hangzhou", "weather": "晴天"}。 用戶只需要在加載提前在頁面定義好handleResponse這個(gè)全局函數(shù)略贮,在函數(shù)內(nèi)部處理參數(shù)即可甚疟。

<script>
function handlerResponse(ret){
console.log(ret);
}
</script>
<script src="http://freegeoip.net/json/?callback=handleResponse"></script>

總結(jié):
JSONP是通過 script 標(biāo)簽加載數(shù)據(jù)的方式去獲取數(shù)據(jù)當(dāng)做 JS 代碼來執(zhí)行 提前在頁面上聲明一個(gè)函數(shù)仗岖,函數(shù)名通過接口傳參的方式傳給后臺,后臺解析到函數(shù)名后在原始數(shù)據(jù)上「包裹」這個(gè)函數(shù)名览妖,發(fā)送給前端轧拄。換句話說,JSONP 需要對應(yīng)接口的后端的配合才能實(shí)現(xiàn)讽膏。

栗子

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div class="container">
        <ul class="news">
        </ul>
        <button class="show">show news</button>
    </div>
</body>
<script>

    $('.show').addEventListener('click', function () {
        var script = document.createElement('script');
        script.src = 'http://127.0.0.1:8080/getNews?callback=handleResponse';
        document.head.appendChild(script);
        document.head.removeChild(script);
    })

    function handleResponse(news) {
        var html = '';
        for (var i = 0; i < news.length; i++) {
            html += '<li>' + news[i] + '</li>';
        }
        console.log(html);
        $('.news').innerHTML = html;
    }

    function $(id) {
        return document.querySelector(id);
    }

</script>
</html>

我們創(chuàng)建了一個(gè)<script>節(jié)點(diǎn)檩电,并向其src屬性賦值為一個(gè)跨域URL的Ajax請求(見node server)。在document文檔頭部添加了這個(gè)節(jié)點(diǎn)府树,當(dāng)文檔加載運(yùn)行時(shí)俐末,一旦文檔樹中有引用script標(biāo)簽,都會將其下載下來奄侠,所以屆時(shí)會自動下載script腳本卓箫,只不過這個(gè)腳本的本質(zhì)是JSON。removeChild是為了加載script后在移除它垄潮,保持整體語義烹卒。

接下來定義callback,這里回調(diào)函數(shù)的作用就是接受JSON字符串弯洗,作為參數(shù)進(jìn)入函數(shù)轉(zhuǎn)化為字符串旅急,進(jìn)而轉(zhuǎn)換為HTML元素內(nèi)容等等。而為其“包裹”上回調(diào)函數(shù)的行為牡整,是在后端進(jìn)行的

node server

var http = require("http");
var fs = require("fs");
var path = require("path");
var url = require("url");

var server = http.createServer(function (req, res) {

    var pathObj = url.parse(req.url, true);

    switch (pathObj.pathname) {
        case "/getNews":
            var news = ["News-A", "News-B", "News-C"];
            res.setHeader("Content-Type", "text/json; charset=utf-8");

            console.log(pathObj);                   // '/getNews?callback=handleResponse'
            console.log(pathObj.query.callback);    // handleResponse

            if (pathObj.query.callback) {
                res.end(pathObj.query.callback + "(" + JSON.stringify(news) + ")");
                // handleResponse(["News-A","News-B","News-C"])    服務(wù)端返回的數(shù)據(jù)
            } else {
                res.end(JSON.stringify(news));
            }
            break;

        default:
            fs.readFile(path.join(__dirname, pathObj.pathname), function (e, data) {
                if (e) {
                    res.writeHead(404, "not found");
                    res.end("<h1>404 Fot Found</h1>")
                } else {
                    res.end(data);
                }
            })
    }

});

server.listen(8080);
console.log("visit http://localhost:8080/httpServer.html");

node代碼如上坠非,我們這里模擬的JSON是一個(gè)新聞列表。模擬了一個(gè)/getNews/URL果正,使用script作為JS腳本下載并執(zhí)行。
如果存在一個(gè)回調(diào)"值"盟迟,就把此值當(dāng)做作為JS函數(shù)名秋泳,并將JSON字符串傳遞進(jìn)來。

點(diǎn)擊后攒菠,我們就看到了一個(gè)來自跨域的HTML內(nèi)容迫皱。

JSONP之所以在開發(fā)人員中極為流行,主要原因是它非常簡單易用辖众。與圖像Ping相比卓起,它的優(yōu)點(diǎn)在于能夠直接訪問響應(yīng)文本,支持在瀏覽器與服務(wù)器之間雙向通信凹炸。不過戏阅,JSONP也有兩點(diǎn)不足:

  • 首先JSONP是從其他域中加載代碼執(zhí)行,如果其他域不安全啤它,很可能會在響應(yīng)中夾帶一些惡意代碼奕筐,而此時(shí)除了完全放棄JSONP調(diào)用之外舱痘,沒有辦法追究。因此在使用不是你自己運(yùn)維的Web服務(wù)時(shí)离赫,一定保證安全可靠芭逝。
  • 確定JSONP請求是否失敗不容易
    ————《JS高程》

跨域 —— CORS

通過XHR實(shí)現(xiàn)Ajax通信的一個(gè)主要限制,來源于跨域安全策略渊胸。默認(rèn)情況下旬盯,XHR對象只能訪問包含它的頁面位于同一個(gè)域中的資源。這種安全策略可以預(yù)防某些惡意行為翎猛。但是胖翰,實(shí)現(xiàn)合理的跨域請求對開發(fā)某些瀏覽器應(yīng)用程序也是直觀重要的。
CORS(Cross-Origin Resource Sharing办成,跨域資源共享)是W3C的一個(gè)工作草案泡态,定義了在必須訪問跨域資源時(shí),瀏覽器與服務(wù)器應(yīng)該如何溝通迂卢。CORS背后思想某弦,就是使用自定義的HTTP頭部讓瀏覽器與服務(wù)器進(jìn)行溝通,從而決定請求或響應(yīng)是應(yīng)該成功而克,還是應(yīng)該失敗靶壮。
比如一個(gè)簡單的使用GET和POST發(fā)送的請求,它沒有自定義的頭部员萍,而主體內(nèi)容是text/plain腾降。在發(fā)送該請求時(shí),需要給它附加一個(gè)額外的Origin頭部碎绎,其中包含請求頁面的源信息(協(xié)議螃壤、域名和端口),以便服務(wù)器根據(jù)這個(gè)頭部信息來決定是否給予響應(yīng)筋帖。

總結(jié):使用 XMLHttpRequest 發(fā)送請求時(shí)奸晴,瀏覽器發(fā)現(xiàn)該請求不符合同源策略,會給該請求加一個(gè)請求頭:Origin日麸,后臺進(jìn)行一系列處理寄啼,如果確定接受請求則在返回結(jié)果中加入一個(gè)響應(yīng)頭:Access-Control-Allow-Origin; 瀏覽器判斷該相應(yīng)頭中是否包含 Origin 的值,如果有則瀏覽器會處理響應(yīng)代箭,我們就可以拿到響應(yīng)數(shù)據(jù)墩划,如果不包含瀏覽器直接駁回,這時(shí)我們無法拿到響應(yīng)數(shù)據(jù)嗡综。

栗子
HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div class="container">
        <ul class="news">
        </ul>
        <button class="show">show news</button>
    </div>
</body>
<script>
    $('.show').addEventListener('click', function () {
        var xhr = new XMLHttpRequest()
        xhr.open('GET', 'http://127.0.0.1:8080/getNews', true)
        xhr.send();
        xhr.onload = function () {
            appendHtml(JSON.parse(xhr.responseText))
        }
    })
 
    function appendHtml(news) {
        var html = '';
        for (var i = 0; i < news.length; i++) {
            html += '<li>' + news[i] + '</li>';
        }
        $('.news').innerHTML = html;
    }

    function $(selector) {
        return document.querySelector(selector)
    }

</script>
</html>

node server

var http = require("http");
var fs = require("fs");
var path = require("path");
var url = require("url");

var server = http.createServer(function (req, res) {

    var pathObj = url.parse(req.url, true);

    switch (pathObj.pathname) {
        case "/getNews":
            var news = ["News-A", "News-B", "News-C"];
            res.setHeader('Access-Control-Allow-Origin','http://localhost:8080');
            res.end(JSON.stringify(news));
            break;

        default:
            fs.readFile(path.join(__dirname, pathObj.pathname), function (e, data) {
                if (e) {
                    res.writeHead(404, "not found");
                    res.end("<h1>404 Fot Found</h1>")
                } else {
                    res.end(data);
                }
            })
    }
});
server.listen(8080);
console.log("visit http://localhost:8080/httpServer.html");

HTML中發(fā)送Ajax請求后http://127.0.0.1:8080/getNews乙帮,會在請求頭部自動加上Origin。


之后在后端設(shè)置了一個(gè)res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')蛤高,表示接受來自http://localhost:8080的請求蚣旱,在響應(yīng)頭部中打上了Access-Control-Allow-Origin標(biāo)記碑幅,返回?cái)?shù)據(jù)后,瀏覽器會識別標(biāo)記塞绿,確認(rèn)通過拿到數(shù)據(jù)沟涨。

我們修改一下,假設(shè)后端只接收端口9000的跨域請求异吻,那么會這樣裹赴。



服務(wù)器一樣會返回請求,在Preview中我們可以看到诀浪,但是請求域的端口不一樣棋返,于是被瀏覽器攔截了。

如若想通過任何域的請求雷猪,可以這樣設(shè)置:

 res.setHeader('Access-Control-Allow-Origin','*');

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末睛竣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子求摇,更是在濱河造成了極大的恐慌射沟,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件与境,死亡現(xiàn)場離奇詭異验夯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)摔刁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門挥转,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人共屈,你說我怎么就攤上這事绑谣。” “怎么了拗引?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵域仇,是天一觀的道長。 經(jīng)常有香客問我寺擂,道長,這世上最難降的妖魔是什么泼掠? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任怔软,我火速辦了婚禮,結(jié)果婚禮上择镇,老公的妹妹穿的比我還像新娘挡逼。我一直安慰自己,他們只是感情好腻豌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布家坎。 她就那樣靜靜地躺著嘱能,像睡著了一般。 火紅的嫁衣襯著肌膚如雪虱疏。 梳的紋絲不亂的頭發(fā)上惹骂,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音做瞪,去河邊找鬼对粪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛装蓬,可吹牛的內(nèi)容都是我干的著拭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼牍帚,長吁一口氣:“原來是場噩夢啊……” “哼儡遮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起暗赶,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤鄙币,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后忆首,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爱榔,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年糙及,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了详幽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浸锨,死狀恐怖唇聘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柱搜,我是刑警寧澤迟郎,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站聪蘸,受9級特大地震影響宪肖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜健爬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一控乾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧娜遵,春花似錦蜕衡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽久脯。三九已至,卻和暖如春镰吆,著一層夾襖步出監(jiān)牢的瞬間帘撰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工鼎姊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骡和,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓相寇,卻偏偏與公主長得像慰于,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子唤衫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容