跨域, 什么是跨域?跨域有什么好处?跨域有什么很差?怎么实现跨域?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...");
复制代码
欢迎关注微信公众号!,进行更好的交流。
复制代码