跨域的产生
不用多讲,作为一名前端开发人员,相信大家都知道跨域是因为浏览器的同源策略所导致的。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。浏览器引入同源策略主要是为了防止XSS,CSRF攻击。
在同源策略影响下,域名A向域名B发送Ajax请求,或操作Cookie、LocalStorage、indexDB等数据,或操作dom,js就会受到限制,但请求css,js等静态资源不受限制
跨域的解决方案
1 通过jsonp跨域
首先说一下jsonp的原理,例如我们平时写html的时候常常会使用
<script src="www.b.com/js/jquery.js"></script>这种方式去取放在另外服务器上的静态资源,这个是不受同源策略所限制的,所以我们利用这一点可以解决跨域的问题。
主要代码如下:
1.1原生实现
在www.a.com域名写下如下代码,去请求www.b.com域名的数据
<script>
var script = document.creatElement('script');
script.type = 'text/javascript';
script.src = 'http://www.b.com/getdata?callback=demo';
function demo(res){
console.log(res);
}
</script>
这里,我们利用动态脚本的src属性,变相地发送了一个http://www.b.com/getdata?call...。这时候,b.com页面接受到这个请求时,如果没有JSONP,会正常返回json的数据结果,像这样:{ msg: 'helloworld' },而利用JSONP,服务端会接受这个callback参数,然后用这个参数值包装要返回的数据:demo({msg: 'helloworld'});
这时候,如果a.com的页面上正好有一个demo 的函数:
function demo(res){
console.log(res);
}
当远程数据一返回的时候,随着动态脚本的执行,这个demo函数就会被执行。
1.2 jquery ajax请求实现
$.ajax({
url:'http://www.b.com/getdata',
type:'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: 'demo', // 自定义回调函数名
data: {}
});
服务端代码实现:
以nodejs为例
var http = require(http);
//引入url模块解析url字符串
var url = require('url);
//引入querystring模块处理query字符串
var querystring = require('querystring');
var server = http.createServer();
server.on('request',function(req,res){
var urlPath = url.parse(req.url).pathname;
var param = querystring .parse(req.url.split('?')[1]);
if(urlPath === '/getData' && param.callback) {
res.writeHead(200,{'Content-Type','application/json;charset=utf-8'});
var data = { msg: 'helloworld' };
data = JSON.stringify(data );
var callback = param .callback+'('+data+');';
res.write(callback);
res.end();
} else {
res.writeHead(200, {'Content-Type':'text/html;charset=utf-8'});
res.write('Hell World\n');
res.end();
}
})
2 CORS 跨域资源共享
2.1 简单请求和非简单请求
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
简单请求同时满足以下条件,只要不满足以下条件的则为非简单请求
2.2 进行带有身份凭证的CORS 请求
- 默认情况下的跨域请求都是不会把cookie发送给服务器的,在需要发送的情况下,如果是xhr,那么需要设置xhr.withCredentials=true,
- 如果是采用fetch获取的话,那么需要在request里面设置 credentials:'include',
- 但是如果服务器在预请求的时候没返回Access-Control-Allow-Crenditials:true的话,那么在实际请求的时候,cookie是不会被发送给服务器端的,要特别注意对于简单的get请求,不会有预请求的过程,
- 那么在实际请求的时候,如果服务器没有返回Access-Control-Allow-Crenditials:true的话那么响应结果浏览器也不会交给请求者
2.3 HTTP 响应首部字段
- Access-Control-Allow-Origin: <origin> | *
- Access-Control-Expose-Headers 头让服务器把允许浏览器访问的头放入白名单
- Access-Control-Max-Age 头指定了preflight请求的结果能够被缓存多久
- Access-Control-Allow-Credentials
头指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。 - Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。
- Access-Control-Allow-Headers 首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。
2.4 以nodejs express为例,说明如何使用cors解决跨域
var express=require('express');
var url=require('url');
var app=express();
var allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:63342');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Credentials','true');
next();
};
app.use(allowCrossDomain);
app.get('/getData',function (req,res,next) {
var queryValue=url.parse(req.url).query;
if(queryValue==='[email protected]'){
res.send(true);
}else {
res.send(false);
}
});
app.listen(3001);
3 window.postMessage
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
- iframe嵌套页面跨域通信
- 页面和其打开的新窗口的通信
- 多窗口之间消息传递
用法:
postMessage(data,origin)方法接受两个参数,
data:需要传递的数据,html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin:协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
<iframe id="iframe" src="http://www.b.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'jianjian'
};
// 向http://www.b.com传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data),'http://www.b.com');
};
// 接受http://www.b.com返回数据
window.addEventListener('message', function(e) {
alert('data from http://www.b.com---> ' + e.data);
}, false);
</script>
<script>
// 接收http://www.a.com/a.html的数据
window.addEventListener('message', function(e) {
alert('data from http://www.a.com/a.html---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回http://www.a.com/a.html
window.parent.postMessage(JSON.stringify(data), 'http://www.a.com');
}
}, false);
</script>
4 document.domain
这种方式只适合主域名相同,但子域名不同的iframe跨域。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
<iframe id="iframe" src="http://www.child.a.com/b.html" style="display:none;"></iframe>
<script>
document.domain = 'a.com';
var a = 'hello world';
</script>
"http://www.child.a.com/b.html
<script>
document.domain = 'a.com';
var b = window.parent.a;
console.log(b);
</script>
5 window.name
具体实现:
http://www.a.com/a.html 主页面
http://www.b.com/b.html 数据页面
http://www.a.com/proxy.html 代理页面
<script>
function crosDomainGetData(url,callback){
var state = 0;
var iframe = document.createElement('iframe);
iframe.src = url;
iframe.onload = function(){
if(state === 1){
//代理页面成功过后,读取window.name
var data = iframe.contentWindow.name;
callback&&callback(data);
//销毁iframe
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
} else {
//第一次加载数据页面成功后,切换代理页面
state = 1;
iframe.contentWindow.location = 'http://www.a.com/proxy.html';
}
}
document.body.appendChild(iframe);
}
crosDomainGetData('http://www.b.com/b.html',function(data){
alert(data);
})
</script>
window.name = '123'
6 nginx代理跨域
server{
# 监听8080端口
listen 8080;
# 域名是localhost
server_name localhost;
#凡是localhost:8080/api这个样子的,都转发到真正的服务端地址http://www.b.com:8080
location ^~ /api {
proxy_pass http://www.b.com:8080;
}
}
7 WebSocket协议跨域
websoket协议天然支持跨域,你只需要学会如何使用它即可,关于websocket协议请看我的另外一篇文章WebSocket网络通信协议
参考文章:
https://developer.mozilla.org...
https://segmentfault.com/a/11...