跨域, 什麼是跨域?跨域有什麼好處?跨域有什麼很差?怎麼實現跨域?javascript
只要協議、域名、端口有任何一個不一樣,都被看成是不一樣的域,之間的請求就是跨域操做. 對於端口和協議的不一樣,只能經過後臺來解決。php
防止CSRF攻擊、同源策略:隔離潛在惡意文件的重要安全機制html
補充知識:java
什麼是CSRF攻擊?node
CSRF(Cross-site request forgery 跨站請求僞造,也被稱爲「One Click Attack」或者Session Riding,一般縮寫爲 CSRF 或者 XSRF,是一種對網站的惡意利用。儘管聽起來像跨站腳本(XSS),但它與 XSS 很是不一樣,而且攻擊方式幾乎相左。XSS 利用站點內的信任用戶,而CSRF則經過假裝來自受信任用戶的請求來利用受信任的網站。與 XSS 攻擊相比,CSR F攻擊每每不大流行(所以對其進行防範的資源也至關稀少)和難以防範,因此被認爲比XSS更具危險性。 爲何會出現CSRF攻擊?jquery
舉例說明ios
好比說有兩個網站 A 和 B。你是 A 網站的管理員,你在 A 網站有一個權限是刪除用戶,好比說這個過程只需用你的身份登錄而且 POST 數據到 【a.com/delUser 】, 就能夠實現刪除操做。好如今說B網站,B網站被攻擊了,別人種下了惡意代碼,你點開的時候就會模擬跨域請求,若是是針對你,那麼就能夠模擬對 A 站的跨域請求,剛好這個時候你已經在 A 站登錄了。那麼攻擊者 在B 站內經過腳本,模擬一個用戶刪除操做是很簡單的。面對這種問題,有從瀏覽器解決,但我的認爲最好是從網站端解決,檢測每次 POST 過來數據時的 Refer,添加AccessToken 等都是好方法。web
防範 CSRF 攻擊能夠遵循如下幾種規則:ajax
什麼是 XSS 攻擊chrome
Cross-Site Scripting(跨站腳本攻擊)簡稱 XSS,是一種代碼注入攻擊。XSS 能夠分爲多種類型,可是整體上我認爲分爲兩類:持久型、非持久型和DOM-Based型 XSS. 持久型也就是攻擊的代碼被服務端寫入進數據庫中,這種攻擊危害性很大,由於若是網站訪問量很大的話,就會致使大量正常訪問頁面的用戶都受到攻擊。 一次性(非持久性) 經過用戶點擊連接引發
XSS 主要作什麼事:
防範措施: 針對 URL 編碼, HTML編碼, JS 編碼。 URL只能使用英文字母(a-zA-Z)、數字(0-9)、-_.~4個特殊字符以及全部(; , /?:@&=+$#)保留字符。 例如:
// 使用了漢字
var url1 = 'http://www.帥.com';
而後因爲 encodeURI 不轉義 & 、 ? 和 = 。
使用encodeURLComponent
// "http://a.com?a=%3F%26"
encodeURI('http://a.com') + '?a=' + encodeURIComponent('?&');
相應的解碼
decodeURl()
decodeURLComponent()
複製代碼
判斷輸入格式: 過濾特殊字符:<、 > 、&、
過濾危險字符: 去除<"script"> 、javascript、onclik
/** * 轉義 HTML 特殊字符 * @param {String} str */
function htmlEncode(str) {
return String(str)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
// 正則獲取危險標籤
var REGEXP_TAG = /<(script|style|iframe)[^<>]*?>.*?<\/\1>/ig;
// 正則獲取危險標籤屬性
var REGEXP_ATTR_NAME = /(onerror|onclick)=([\"\']?)([^\"\'>]*?)\2/ig;
/** * 過濾函數 */
function filter(str) {
return String(str)
.replace(REGEXP_TAG, '')
.replace(REGEXP_ATTR_NAME, '');
}
複製代碼
或者使用 CSP 創建白名單,開發者明確告訴瀏覽器哪些外部資源能夠加載和執行。 兩種方式開啓 CSP。
Content-Security-Policy: default-src ‘self’
Content-Security-Policy: img-src https://*
Content-Security-Policy: child-src 'none'
JSONP跨域GET請求是一個經常使用的解決方案,下面咱們來看一下JSONP跨域是如何實現的,而且探討下JSONP跨域的原理。 利用在頁面中建立"script"節點的方法向不一樣域提交HTTP請求的方法稱爲JSONP,這項技術能夠解決跨域提交Ajax請求的問題。
JSONP的工做原理以下所述:
假設在http://example1.com/index.php這個頁面中向http://example2.com/getinfo.php提交GET請求,咱們能夠將下面的JavaScript代碼放在http://example1.com/index.php這個頁面中來實現: 代碼以下:
//server.js
const url = require('url');
require('http').createServer((req, res) => {
// console.log('req: ', req);
const data = {
x: 10
};
console.log('url.parse(req.url, true).query: ', url.parse(req.url, true).query);
const callback = url.parse(req.url, true).query.callback;
res.writeHead(200);
res.end( `${callback}(${JSON.stringify(data)})` ); // jsonpCallback(data)
}).listen(3000, '127.0.0.1');
console.log('啓動服務,監聽 127.0.0.1:3000');
//html
<
script >
function jsonpCallback(data) {
alert('得到 X 數據:' + data.x);
} <
/script> <
script src = "http://127.0.0.1:3000?callback=jsonpCallback" > < /script>
複製代碼
當GET請求從http://xx1.html返回時,能夠返回一段JavaScript代碼,這段代碼會自動執行,能夠用來負責調用http://xxx2.html頁面中的一個callback函數。
JSONP的優勢是:它不像XMLHttpRequest對象實現的Ajax請求那樣受到同源策略的限制;它的兼容性更好,在更加古老的瀏覽器中均可以運行,不須要XMLHttpRequest或ActiveX的支持;而且在請求完畢後能夠經過調用 callback 的方式回傳結果。
JSONP的缺點則是:它只支持GET請求而不支持POST等其它類型的HTTP請求;它只支持跨域HTTP請求這種狀況,不能解決不一樣域的兩個頁面之間如何進行 JavaScript 調用的問題。 再來一個例子:
//server.js
const url = require('url');
require('http').createServer((req, res) => {
// console.log('req: ', req);
const data = {
"x": "10"
};
console.log('url.parse(req.url, true).query: ', url.parse(req.url, true).query);
if (url.parse(req.url, true).query.word === 'sjh') {
const callback = url.parse(req.url, true).query.callback;
res.writeHead(200);
res.end( `${callback}(${JSON.stringify(data)})` );
}
}).listen(3000, '127.0.0.1');
console.log('啓動服務,監聽 127.0.0.1:3000');
//html
var qsData = {
'word': 'shj'
};
$.ajax({
async: false,
url: "http://127.0.0.1:3000", //跨域的dns
type: "GET",
dataType: 'jsonp',
jsonp: 'callback',
data: qsData,
timeout: 5000,
success: function(json) {
console.log('json: ', json);
alert(json.x)
// let obj = JSON.parse(json);
// console.log('obj: ',obj );
},
error: function(xhr) {
console.log('xhr: ', xhr);
//請求出錯處理
alert("請求出錯)", xhr);
}
});
複製代碼
這種方式實際上是上例.ajax api底層的參數就被封裝而不可見了。 jquery .ajax方法就和ajax XmlHttpRequest沒什麼關係了,這種跨域方式其實與ajax XmlHttpRequest協議無關了。 取而代之的則是JSONP協議。JSONP是一個非官方的協議,它容許在服務器端集成Script tags返回至客戶端,經過javascript callback的形式實現跨域訪問。
Jsonp的執行過程以下: 首先在客戶端註冊一個callback (如:'jsoncallback'), 而後把callback的名字(如:jsonp1236827957501)傳給服務器。注意:服務端獲得callback的數值後,要用jsonp1236827957501(......)把將要輸出的json內容包括起來,此時,服務器生成 json 數據才能被客戶端正確接收。
而後以 javascript 語法的方式,生成一個function, function 名字就是傳遞上來的參數 'jsoncallback'的值 jsonp1236827957501 . 最後將 json 數據直接以入參的方式,放置到 function 中,這樣就生成了一段 js 語法的文檔,返回給客戶端。
客戶端瀏覽器,解析script標籤,並執行返回的 javascript 文檔,此時javascript文檔數據,做爲參數, 傳入到了客戶端預先定義好的 callback 函數(如上例中jquery $.ajax()方法封裝的的success: function (json))裏。 能夠說jsonp的方式原理上和是一致的(qq空間就是大量採用這種方式來實現跨域數據交換的)。JSONP是一種腳本注入(Script Injection)行爲,因此有必定的安全隱患。 那jquery爲何不支持post方式跨域呢?
跨域資源共享CORS
什麼是CORS CORS(cross-Origin Resource Sharing) 跨域資源共享,管理跨源請求。而跨域資源共享是有益的。今天的咱們建立的大多數網站加載資源從網絡的各個不一樣的地方, 當咱們發送 GET 請求時,大多數狀況瀏覽器頭會fan hui Access-Control-Allow-Origin: * 。意思是可以共享資源在任何域名下。不然就是隻能在特定的狀況下了。 當請求時如下時,將在原始請求前先進行標準預請求,使用 OPTIONS 頭,
http: //www.example.com/foo-bar.html
複製代碼
瀏覽器訪問外部資源時會出現下面這種狀況
而咱們想要的效果是這種狀況.
咱們能夠在 HTTP 請求頭加上 CORS 標準。
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Expose-Headers
Access-Control-Max-Age
Access-Control-Request-Headers
Access-Control-Request-Method
Origin
當咱們發送 GET 請求時,大多數狀況瀏覽器頭會返回 Access-Control-Allow-Origin: * 。意思是可以共享資源在任何域名下。不然就是隻能在特定的狀況下了。
app.use(cors()); 當請求時如下時,將在原始請求前先進行標準預請求,使用 OPTIONS 頭,
當咱們發的預處理請求並指示原始請求是否安全,若是指定原始請求時安全的,則它將容許原始請求。不然拒接。
//npm install cors
response.setHeader('Content-Type', 'text/html');
var express = require('express');
var cors = require('cors');
//或者直接設置
// res.writeHead(200, {
//'Access-Control-Allow-Origin': 'http://localhost:8080'
// });
var app = express();
app.use(cors());
app.get('/hello/:id', function(req, res, next) {
res.json({
msg: 'CORS-enabled!'
});
});
app.listen(80, function() {
console.log('CORS-enabled web ');
});
複製代碼
Server Proxy:
服務器代理,當你須要有跨域請求額操做時發送給後端,讓後端幫你帶爲請求,而後將最後的獲取的結果發送給你。
//html
$.get('http://127.0.0.1:3000/topics', function(data) {
consoel.log(data)
})
//server.js
const url = require('url');
const http = require('http');
const server = http.createServer((req, res) => {
const path = url.parse(req.url).path.slice(1);
console.log('path: ', path);
if (path === 'topics') {
http.get('http://cnodejs.org/api/v1/topics', (resp) => {
const {
statusCode
} = resp;
const contentType = resp.headers['content-type'];
console.log('resp: ', statusCode, contentType);
let error;
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}` );
} else if (!/^application\/json/.test(contentType)) {
error = new Error('Invalid content-type.\n' +
`Expected application/json but received ${contentType}` );
}
if (error) {
console.error(error.message);
// Consume response data to free up memory
resp.resume();
return;
}
res.setEncoding('utf8');
let data = "";
resp.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
console.log(parsedData);
} catch (e) {
console.error(e.message);
}
});
})
}
}).listen(3000, '127.0.0.1');
console.log('啓動服務,監聽 127.0.0.1:3000');
複製代碼
基於iframe實現的跨域要求兩個域具備aa.xx.com, bb.xx.com這種特色,也就是兩個頁面必須屬於一個基礎域(例如都是xxx.com,或是xxx.com.cn),使用同一協議(例如都是 http)和同一端口(例如都是80),這樣在兩個頁面中同時添加document.domain,就能夠實現父頁面調用子頁面的函數,固然這種方法只能解決主域相同而二級域名不一樣的狀況。 代碼以下:
//頁面一在head內添加js以下:
document.domain = 「xx.com」;
function aa() {
alert(「p」);
}
//body添加iframe和js以下
<
iframe src = 」http: //localhost:8080/2.html「 id=」i」>
document.getElementById(‘i’).onload = function() {
var d = document.getElementById(‘i’).contentWindow;
d.a();
};
//頁面二 head添加以下
document.domain = 「xx.com」;
function a() {
alert(「c」);
}
//這時候父頁面就能夠調用子頁面的a函數,實現js跨域訪問
複製代碼
6.一、經過location.hash跨域
如下三種都是經過 iframe 方式來,咱們根據 iframe 可以在瀏覽器下可以跨域的特色,進行通訊。
在 url 中,www.a.com#la 的 "#la" 就是 location.hash,改變 hash 值不會致使頁面刷新,因此能夠利用 hash 值來進行數據的傳遞,固然數據量是有限的。
//locationNameA
<
script >
function startRequest() {
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'http://127.0.0.1:5501/locationNameB.html#name';
document.body.appendChild(ifr);
}
function checkHash() {
try {
var data = location.hash ? location.hash.substring(1) : '';
if (console.log) {
console.log('Now the data is ' + data);
}
} catch (e) {};
}
setInterval(checkHash, 2000);
<
/script>
//locationNameB
<
script >
function callBack() {
try {
parent.location.hash = 'somedata';
} catch (e) {
// ie、chrome的安全機制沒法修改parent.location.hash,
// 因此要利用一箇中間的cnblogs域下的代理iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'http://127.0.0.1:5502/locationNameC.html#sjh';
document.body.appendChild(ifrproxy);
}
}
<
/script>
//locationNameC
<
script >
parent.parent.location.hash = self.location.hash.substring(1);
console.log('parent.parent.location.hash: ', parent); <
/script>
複製代碼
6.2 經過window.name跨域
window對象有個 name 屬性,該屬性有個特徵:即在一個窗口(window)的生命週期內, 窗口載入的全部的頁面都是共享一個window.name 的,每一個頁面對 window.name 都有讀寫的權限,window.name 是持久存在一個窗口載入過的全部頁面中的。window.name 屬性的 name 值在不一樣的頁面(甚至不一樣域名)加載後依舊存在(若是沒修改則值不會變化),而且能夠支持很是長的 name 值(2MB)。
window.name = data; //父窗口先打開一個子窗口,載入一個不一樣源的網頁,該網頁將信息寫入。
window.location = 'http://a.com/index.html';//接着,子窗口跳回一個與主窗口同域的網址。
var data = document.getElementById('myFrame').contentWindow.name。//而後,主窗口就能夠讀取子窗口的window.name了。
複製代碼
若是是與iframe通訊的場景就須要把iframe的src設置成當前域的一個頁面地址。
//http://127.0.0.1:5502/window.nameA.html
let data = '';
const ifr = document.createElement('iframe');
ifr.src = "http://127.0.0.1:5501/window.nameB.html";
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function() {
ifr.onload = function() {
console.log('ifr。contentWindow: ', ifr.contentWindow, ifr.contentWindow.name);
data = ifr.contentWindow.name;
console.log('收到數據:', data);
}
ifr.src = "http://127.0.0.1:5502/window.nameC.html";
} <
/script>
//http://127.0.0.1:5501/window.nameB.html
<
script >
window.name = "你想要的數據!"; <
/script>
//http://127.0.0.1:5502/window.nameC.html
emty
複製代碼
6.3 windows.postMessage
otherWindow.postMessage(message, targetOrigin, [transfer]);
接收數據:data、type:類型、source:對象、origin:源;
//postMessageChild.html
<
iframe src = "./postMessageChild.html"
id = "myFrame" > < /iframe> <
script >
iframe = document.getElementById('myFrame')
iframe.onload = function() {
iframe.contentWindow.postMessage('MessageFromIndex1', '*')
setTimeout(() => {
iframe.contentWindow.postMessage('MessageFromIndex2', '*')
})
}
function receiveMessageFromIframePage(event) {
console.log('receiveMessageFromIframePage', event)
}
window.addEventListener('messae', receiveMessageFromIframePage, false)
// postMessageParent.html
parent.postMessage({
msg: 'MessageFromIframePage'
}, "*");
function receiveMessageFromParent(event) {
consle.log('receiveMessageFromParent', event)
}
widnow.addEventListener('message', receiveMessageFromParent, false)
複製代碼
WebSocket是一種通訊協議,使用ws://(非加密)和wss://(加密)做爲協議前綴。該協議不實行同源政策,只要服務器支持,就能夠經過它進行跨源通訊。
//html
<
ul > < /ul> <
input type = "text" >
$(function() {
var iosocket = io.connect('http://localhost:3000/');
var $ul = $("ul");
var $input = $("input");
iosocket.on('connect', function() { //接通處理
$ul.append($('<li>連上啦</li>'));
iosocket.on('message', function(message) { //收到信息處理
$ul.append($('<li></li>').text(message));
});
iosocket.on('disconnect', function() { //斷開處理
$ul.append('<li>Disconnected</li>');
});
});
$input.keypress(function(event) {
if (event.which == 13) { //回車
event.preventDefault();
console.log("send : " + $input.val());
iosocket.send($input.val());
$input.val('');
}
});
});
//server.js
var io = require('socket.io')(3000);
io.sockets.on('connection', function(client) {
client.on('message', function(msg) { //監聽到信息處理
console.log('Message Received: ', msg);
client.send('服務器收到了信息:' + msg);
});
client.on("disconnect", function() { //斷開處理
console.log("client has disconnected");
});
});
console.log("listen 3000...");
複製代碼
歡迎關注微信公衆號!,進行更好的交流。
複製代碼