什么是跨域
跨域,是指瀏覽器不能執(zhí)行其它網(wǎng)站的腳本,它是由瀏覽器的同源策略造成的,防止惡意攻擊,是瀏覽器對javascript實(shí)施的安全限制.
什么是同源策略
同源策略是一個重要的安全策略,它用于限制一個origin的文檔或者它加載的腳本如何能與另一個源的資源進(jìn)行交互,它能幫助阻隔惡意文檔,減少可能被攻擊的媒體.
同源策略限制的行為
1.Cookie、LocalStorage和IndexDB無法讀取
2.DOM和JS對象無法獲取
3.Ajax請求不能發(fā)送
什么是同源
同源就是指域名、協(xié)議、端口均為相同.
地址
http(協(xié)議)://store.company.com(域名):81(端口)
URL | 結(jié)果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 同源 | 只有路徑不同 |
http://store.company.com/dir/inner/another.html | 同源 | 只有路徑不同 |
https://store.company.com/secure.html | 失敗 | 協(xié)議不同 |
http://store.company.com:81/dir/etc.html | 失敗 | 端口不同(http://默認(rèn)端口80) |
http://news.company.com/dir/other.html | 失敗 | 域名不同 |
實(shí)現(xiàn)跨域的幾種方式
1.jsonp(只能發(fā)送get請求)
jsonp是JSON with padding簡寫,實(shí)現(xiàn)原理,動態(tài)創(chuàng)建script標(biāo)簽,然后利用script里的src不受同源策略約束通過get請求獲取數(shù)據(jù).利用callback回調(diào)函數(shù)接收服務(wù)器響應(yīng)數(shù)據(jù).
- 代碼示例
var script=document.createElement('script');
script.src="https://api.test.com/v2/list/search?q=javascript&count=1&callback=handleResponse";
document.body.insertBefore(script,document.body.firstChild);
//接收回調(diào)
function handleResponse(response){
console.log(response)
}
2.跨域資源共享(CORS)
CORS是W3C的標(biāo)準(zhǔn),全稱是"跨域資源共享".它允許瀏覽器向跨域的服務(wù)器,發(fā)出XMLHttpRequest請求,從而克服了AJAX只能同源的使用限制.
cors請求分成兩類
1.簡單請求
2.非簡單請求
簡單請求(http方法與請求頭信息的結(jié)合)
1.請求方法是以下三種方法之一.
- HEAD
- GET
- POST
2.HTTP請求頭必須是下面幾種字段
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
對于簡單請求就是瀏覽器直接發(fā)出CORS,具體來說就是請求頭添加Origin字段
GET /cors HTTP/1.1
Origin: http://api.bob.com //本次請求來自于哪個域(協(xié)議 + 域名 + 端口)服務(wù)器根據(jù)這個值,決定是否同意這次請求.
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
如果Origin指定的域名在許可范圍內(nèi),服務(wù)器返回的響應(yīng),會多出幾個頭信息響應(yīng)字段
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
CORS設(shè)置響應(yīng)頭字段
1.Access-Control-Allow-Origin:必選
該字段得值要么是請求時Origin字段的值,要么是一個*,表示接受任意域名的請求.
2.Access-Control-Allow-Credentials:可選
該字段的值是一個布爾值,表示是否允許發(fā)送cookie,默認(rèn)情況下,Cookie不包括在CORS請求中,設(shè)置為true表示服務(wù)器明確許可,瀏覽器可以把Cookie包含在請求中,一起發(fā)送給服務(wù)器,這個值也只能設(shè)置為true,如果服務(wù)器不要瀏覽器發(fā)送Cookie,不發(fā)送該字段即可.
3.Access-Control-Expose-Headers
該字段可選.CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個服務(wù)器返回的基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必須在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值
- 代碼示例
//前端代碼
var xhr=new XMLHttpRequest();
xhr.withCredentials=true; //前端設(shè)置是否帶cookie
xhr.open('post', 'http://www.bai.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
//服務(wù)器端代碼node
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);
// 跨域后臺設(shè)置
res.writeHead(200, {
'Access-Control-Allow-Credentials': 'true', // 后端允許發(fā)送Cookie
'Access-Control-Allow-Origin': 'http://www.bai.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=http://www.bai.com;HttpOnly' // HttpOnly的作用是讓js無法讀取cookie
});
res.write(JSON.stringify(postData));
res.end();
});
});
server.listen('8080');
非簡單請求
凡是不同時滿足上面兩個條件,就屬于非簡單請求,非簡單請求是對服務(wù)器提出特殊的要求,比如請求方法是PUT或DELETE,或者Content-Type字段是application/json.非簡單請求的CORS請求,會在正式通信之前增加一次http查詢請求,稱為"預(yù)檢"請求,瀏覽器先詢問服務(wù)器,當(dāng)前網(wǎng)頁所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些 HTTP 動詞和頭信息字段。只有得到肯定答復(fù),瀏覽器才會發(fā)出正式的XMLHttpRequest請求,否則就報錯。這是為了防止這些新增的請求,對傳統(tǒng)的沒有 CORS 支持的服務(wù)器形成壓力,給服務(wù)器一個提前拒絕的機(jī)會,這樣可以防止服務(wù)器大量收到DELETE和PUT請求,這些傳統(tǒng)的表單不可能跨域發(fā)出的請求。
//前端代碼
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
//預(yù)測http請求頭
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
"預(yù)檢"請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。頭信息里面,關(guān)鍵字段是Origin,表示請求來自哪個源。
除了Origin字段,“預(yù)檢”請求的頭信息包括兩個特殊字段。
1.Access-Control-Request-Method
該字段是必須的,用來列出瀏覽器的 CORS 請求會用到哪些 HTTP 方法,上例是PUT。
2.Access-Control-Request-Headers
該字段是一個逗號分隔的字符串,指定瀏覽器 CORS 請求會額外發(fā)送的頭信息字段,上例是X-Custom-Header
服務(wù)器收到“預(yù)檢”請求以后,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,確認(rèn)允許跨源請求,就可以做出回應(yīng)。
3.document.domain+iframe跨域
此方案僅限主域相同,子域不同的跨域應(yīng)用場景.實(shí)現(xiàn)原理兩個頁面都通過js強(qiáng)制設(shè)置document.domain為基礎(chǔ)主域,就實(shí)現(xiàn)了同域.
- 代碼示例
//父窗口
<iframe id="iframe" src="http://child.domain.com/child.html"></iframe>
<script>
document.domain="domain.com"
var user='admin'
</script>
//子窗口
<script>
document.domain = 'domain.com';
// 獲取父窗口中變量
console.log('get js data from parent ---> ' + window.parent.user);
</script>
4.vue-cli+nginx代理跨域
利用webpack的webpack-dev-server,實(shí)現(xiàn)開發(fā)環(huán)境下,vue渲染服務(wù)和接口代理都是webpack-dev-server同一個,所以頁面與代理之間不需要跨域.
//vue.config.js
devServer:{
open:true,
compress:true,//對所有服務(wù)器啟用gzip壓縮
port:5200,
hot:true,
host:'0.0.0.0',
overlay:{ //錯誤遮罩
warnings: true,
errors: true
},
proxy:{
'/api':{
target: 'www.domain2.com:8080',
changeOrigin: true, //是否跨域
pathRewrite:{
'^/api':'/api'
}
}
}
//nginx
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時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啟用
add_header Access-Control-Allow-Origin http://www.domain1.com; #當(dāng)前端只跨域不帶cookie時,可為*
add_header Access-Control-Allow-Credentials true;
}
}