什么是跨域?
跨域是指一個(gè)域下的文檔或腳本試圖去請(qǐng)求另一個(gè)域下的資源,這里的跨域是廣義的溅话。
廣義的跨域:
- 資源跳轉(zhuǎn):a鏈接晓锻、重定向、表單提交飞几;
- 資源嵌入:<link>砚哆、<script>、<img>屑墨、<frame>等DOM標(biāo)簽
- 腳本請(qǐng)求:ajax躁锁、dom對(duì)象跨域操作等。
狹義的跨域:
其實(shí)我們通常所說的跨域是狹義的卵史,是由瀏覽器同源策略限制的一類請(qǐng)求場(chǎng)景战转。
同源策略/SOP(Same origin policy)是一種約定,由Netscape公司1995年引入瀏覽器以躯,它是瀏覽器最核心也最基本的安全功能槐秧,如果缺少了同源策略,瀏覽器很容易受到XSS寸潦、CSFR等攻擊。所謂同源是指"協(xié)議+域名+端口"三者相同社痛,即便兩個(gè)不同的域名指向同一個(gè)ip地址见转,也非同源。
同源策略限制一下幾種行為:
- Cookie蒜哀、LocalStorage和IndexDB無法獲取
- DOM和JS對(duì)象無法獲得
- Ajax請(qǐng)求不能發(fā)送
常見跨域場(chǎng)景:
場(chǎng)景 | example | 是否允許通信
同一域名斩箫,不同文件或路徑 | http://www.a.com/a | 允許
| http://www.a.com/b |
同一域名,不同端口 | http://www.a.com:8082/a | 不允許
| http://www.a.com/a |
同一域名撵儿,不同協(xié)議 | http://www.a.com/a | 不允許
| https://www.a.com/a |
同一IP,不同域名 | http://www.a.com/a | 不允許
| http://192.168.1.1/a |
主域名相同乘客,子域名不同 | http://www.a.com/a | 不允許
| http://domain.a.com/a |
不同域名, | http://www.a.com/a | 不允許
| http://www.b.com/a |
跨域的解決方案
-
通過jsonp跨域淀歇;
通常為了減輕web服務(wù)器的負(fù)載易核,我們把js、css浪默,img等靜態(tài)資源分離到另一臺(tái)獨(dú)立域名的服務(wù)器上牡直,在html頁面中再通過相應(yīng)的標(biāo)簽從不同域名下加載靜態(tài)資源,而被瀏覽器允許纳决,基于此原理碰逸,我們可以通過動(dòng)態(tài)創(chuàng)建script,再請(qǐng)求一個(gè)帶參網(wǎng)址實(shí)現(xiàn)跨域通信阔加。
原理:
// 前端 <script> var script = document.createElement('script'); script.type = 'text/javascript'; // 傳參并指定回調(diào)執(zhí)行函數(shù)為onBack script.src = 'http://www.a.com/login?user=admin&callback=onBack'; document.head.appendChild(script); // 回調(diào)執(zhí)行函數(shù) function onBack(res) { alert(JSON.stringify(res)); } </script> // 服務(wù)器返回如下(返回時(shí)即執(zhí)行全局函數(shù)) onBack({"status": true, "user": "admin"})
JSONP缺點(diǎn):只能實(shí)現(xiàn)get一種請(qǐng)求饵史。
-
跨域資源共享(CORS);
普通跨域請(qǐng)求:只服務(wù)端設(shè)置Access-Control-Allow-Origin即可,前端無須設(shè)置胳喷,若要帶cookie請(qǐng)求:前后端都需要設(shè)置湃番。(需注意的是:由于同源策略的限制,所讀取的cookie為跨域請(qǐng)求接口所在域的cookie厌蔽,而非當(dāng)前頁牵辣。)
Java后臺(tái):
/* * 導(dǎo)入包:import javax.servlet.http.HttpServletResponse; * 接口參數(shù)中定義:HttpServletResponse response */ // 允許跨域訪問的域名:若有端口需寫全(協(xié)議+域名+端口),若沒有端口末尾不用加'/' response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); // 允許前端帶認(rèn)證cookie:?jiǎn)⒂么隧?xiàng)后奴饮,上面的域名不能為'*'纬向,必須指定具體的域名,否則瀏覽器會(huì)提示 response.setHeader("Access-Control-Allow-Credentials", "true"); // 提示OPTIONS預(yù)檢時(shí)戴卜,后端需要設(shè)置的兩個(gè)常用自定義頭 response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
nodejs后臺(tái):
var http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) { var postData = ''; // 數(shù)據(jù)塊接收中 req.addListener('data', function(chunk) { postData += chunk; }); // 數(shù)據(jù)接收完畢 req.addListener('end', function() { postData = qs.parse(postData); // 跨域后臺(tái)設(shè)置 res.writeHead(200, { 'Access-Control-Allow-Credentials': 'true', // 后端允許發(fā)送Cookie 'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允許訪問的域(協(xié)議+域名+端口) /* * 此處設(shè)置的cookie還是domain2的而非domain1逾条,因?yàn)楹蠖艘膊荒芸缬驅(qū)慶ookie(nginx反向代理可以實(shí)現(xiàn)), * 但只要domain2中寫入一次cookie認(rèn)證投剥,后面的跨域接口都能從domain2中獲取cookie师脂,從而實(shí)現(xiàn)所有的接口都能跨域訪問 */ 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly的作用是讓js無法讀取cookie }); res.write(JSON.stringify(postData)); res.end(); }); }); server.listen('8080'); console.log('Server is running at port 8080...');
CORS也已經(jīng)成為主流的跨域解決方案。
-
nginx代理跨域江锨;
nginx配置解決iconfont跨域吃警。
瀏覽器跨域訪問js、css啄育、img等常規(guī)靜態(tài)資源被同源策略許可酌心,但iconfont字體文件(eot|otf|ttf|woff|svg)例外,此時(shí)可在nginx的靜態(tài)資源服務(wù)器中加入以下配置挑豌。
location / { add_header Access-Control-Allow-Origin *; }
nginx反向代理接口跨域
跨域原理: 同源策略是瀏覽器的安全策略安券,不是HTTP協(xié)議的一部分。服務(wù)器端調(diào)用HTTP接口只是使用HTTP協(xié)議氓英,不會(huì)執(zhí)行JS腳本侯勉,不需要同源策略,也就不存在跨越問題铝阐。
實(shí)現(xiàn)思路:通過nginx配置一個(gè)代理服務(wù)器(域名與domain1相同址貌,端口不同)做跳板機(jī),反向代理訪問domain2接口徘键,并且可以順便修改cookie中domain信息芳誓,方便當(dāng)前域cookie寫入,實(shí)現(xiàn)跨域登錄啊鸭。
nginx具體配置:
#proxy服務(wù)器 server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 index index.html index.htm; # 當(dāng)用webpack-dev-server等中間件代理接口訪問nignx時(shí)锹淌,此時(shí)無瀏覽器參與,故沒有同源限制赠制,下面的跨域配置可不啟用 add_header Access-Control-Allow-Origin http://www.domain1.com; #當(dāng)前端只跨域不帶cookie時(shí)赂摆,可為* add_header Access-Control-Allow-Credentials true; } }
-
nodejs中間件代理跨域挟憔;
node中間件實(shí)現(xiàn)跨域代理,原理大致與nginx相同烟号,都是通過啟一個(gè)代理服務(wù)器绊谭,實(shí)現(xiàn)數(shù)據(jù)的轉(zhuǎn)發(fā),也可以通過設(shè)置cookieDomainRewrite參數(shù)修改響應(yīng)頭中cookie中域名汪拥,實(shí)現(xiàn)當(dāng)前域的cookie寫入达传,方便接口登錄認(rèn)證。
-
webSocket協(xié)議跨域迫筑;
WebSocket protocol是HTML5一種新的協(xié)議宪赶。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信,同時(shí)允許跨域通訊脯燃,是server push技術(shù)的一種很好的實(shí)現(xiàn)搂妻。
-
postMessage跨域;
postMessage是HTML5 XMLHttpRequest Level 2中的API辕棚,且是為數(shù)不多可以跨域操作的window屬性之一欲主,它可用于解決以下方面的問題
- 頁面和其打開的新窗口的數(shù)據(jù)傳遞;
- 多窗口之間消息傳遞逝嚎;
- 頁面與嵌套的iframe消息傳遞扁瓢;
用法:postMessage(data,origin)方法接受兩個(gè)參數(shù)
data: html5規(guī)范支持任意基本類型或可復(fù)制的對(duì)象,但部分瀏覽器只支持字符串补君,所以傳參時(shí)最好用JSON.stringify()序列化引几。
origin: 協(xié)議+主機(jī)+端口號(hào),也可以設(shè)置為"*"赚哗,表示可以傳遞給任意窗口硅堆,如果要指定和當(dāng)前窗口同源的話設(shè)置為"/"屿储。
- a.html
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); iframe.onload = function() { var data = { name: 'aym' }; // 向domain2傳送跨域數(shù)據(jù) iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com'); }; // 接受domain2返回?cái)?shù)據(jù) window.addEventListener('message', function(e) { alert('data from domain2 ---> ' + e.data); }, false); </script>
- b.html
<script> // 接收domain1的數(shù)據(jù) window.addEventListener('message', function(e) { alert('data from domain1 ---> ' + e.data); var data = JSON.parse(e.data); if (data) { data.number = 16; // 處理后再發(fā)回domain1 window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com'); } }, false); </script>