前置知识
什么是跨域
主要是由于浏览器的同源策略引起的,同源策略是浏览器的安全机制,当 协议,域名,端口 三者有一个不同,浏览器就禁止访问资源。
比如:http://www.company.com:80
- http://www.company.com:80/dir/page.html ----成功
- http://www.child.a.com/test/index.html ----失败,域名不同
- https://www.a.com/test/index.html ----失败,协议不同
- http://www.a.com:8080/test/index.html ----失败,端口号不同
同源策略限制以下几种行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM和JS对象无法获得
- AJAX 请求不能发送
不受同源策略限制的有:
- 页面上的链接,比如 a 链接。
- 重定向。
- 表单提交。
- 跨域资源的引入,比如:script, img, link, iframe
这里又有一个问题为什么表单提交不受同源策略限制?
所谓的同源策略,是一个域名在没有得到允许,不能够读取其他域名的内容。但浏览器不阻止你像其他域名发出请求。那么form发送请求能够跨域就可以理解,他只是发出请求,并没有得到响应。
首先,表单的提交方式有两种,一种是直接指定表单的action,一种是ajax接手控制请求。
直接使用action的时候,是直接把请求交给了action里面的域,本身页面不会去管他的请求结果,后面的步骤交给了action里面的域。好比:
<from action="baidu.com">// you form filed
</from>
上面这个表单提交后,剩余的操作就交给了action里面的域baidu.com,本页面的逻辑和这个表单没啥关系,由于不关系请求的响应,所以浏览器认为是安全的。因为表单提交是一个提交数据的过程,表单提交后就会跳转,表单所在的页面是没办法获取结果的。抽象一点说,form提交和点击a标签跳转差不多,没必要对这个做限制。
而使用ajax来控制form的请求的时候,页面js会需要知道请求的返回值,这个时候,浏览器发出跨域请求,需要获得授权才可以成功请求,否则是会拒绝的。如果Ajax表单跨域提交想得到响应结果,目标服务器应该设置Access-Control-Allow-Origin。
9种跨域解决方法
例子中可运行源码
1,JSONP跨域
jsonp的原理就是利用
jsonp有点: JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题 。
jsonp缺点: 仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
1)原生JS实现:
<script>var script = document.createElement('script');script.type = 'text/javascript';// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';document.head.appendChild(script);// 回调执行函数,后端返回的时候会被执行到function handleCallback(res) {alert(JSON.stringify(res));}</script>
服务端返回如下(返回时即执行全局函数):
handleCallback({"success": true, "user": "admin"})
2)jquery Ajax实现:
$.ajax({url: 'http://www.domain2.com:8080/login',type: 'get',dataType: 'jsonp', // 请求方式为jsonpjsonpCallback: "handleCallback", // 自定义回调函数名data: {}
});
3)Vue axios实现:
this.$http = axios;
this.$http.jsonp('http://www.domain2.com:8080/login', {params: {},jsonp: 'handleCallback'
}).then((res) => {console.log(res);
})
后端node.js代码:
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();server.on('request', function(req, res) {var params = querystring.parse(req.url.split('?')[1]);var fn = params.callback;// jsonp返回设置res.writeHead(200, { 'Content-Type': 'text/javascript' });res.write(fn + '(' + JSON.stringify(params) + ')');res.end();
});server.listen('8080');
console.log('Server is running at port 8080...');
2,nginx代理跨域
通过配置文件设置 请求响应头 Access-Control-Allow-Origin…等字段。
1)nginx配置解决 icon font 跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。
location / {add_header Access-Control-Allow-Origin *;
}
2)nginx反向代理接口跨域(不需要前端做什么)Nginx配置学习
server { listen 8080; server_name localhost; location /default/ { proxy_pass http://localhost; }
}
以上的配置中,
listen
表示nginx要监听的端口;
server_name
就是访问nginx时在浏览器中输入的域名,可以直接填ip地址,要绑定多个可以用空格隔开;
location
表示nginx监听该端口时要匹配的url,如果访问nginx的url中包含有/default/就执行代理
proxy_pass
表示nginx要把客户端的请求代理到的目标。
比如:www.github.cn -> www.github.com
#proxy服务器
server {listen 80;server_name www.github.com;location / {proxy_pass www.github.cn; #反向代理proxy_cookie_demo www.github.cn www.github.com;add_header Access-Control-Allow-Origin www.github.cn;add_header Access-Control-Allow-Credentials true;}
}
3,WebSocket协议跨域
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
WebSocket和HTTP都是应用层协议,都基于 TCP 协议。
但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。
同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
前端处理:
// socket.html
<script>let socket = new WebSocket('ws://localhost:3000');socket.onopen = function () {socket.send('heiheihei');//向服务器发送数据}socket.onmessage = function (e) {console.log(e.data);//接收服务器返回的数据}
</script>
服务器端处理:
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//记得安装ws
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {ws.on('message', function (data) {console.log(data);ws.send('嘿嘿嘿')});
})
4,node中间件代理
实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。 代理服务器,需要做以下几个步骤:
- 接受客户端请求 。
- 将请求 转发给服务器。
- 拿到服务器 响应 数据。
- 将 响应 转发给客户端。
5,跨域资源共享(CORS)
CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
服务端设置 Access-Control-Allow-Origin
就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
1) 简单请求
只要同时满足以下两大条件,就属于简单请求
条件1:使用下列方法之一:
- GET
- HEAD
- POST
条件2:Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
请求中的任意 XMLHttpRequestUpload
对象均没有注册任何事件监听器; XMLHttpRequestUpload
对象可以使用 XMLHttpRequest
.upload 属性访问。
2) 复杂请求
不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option
方法的,通过该请求来知道服务端是否允许跨域请求。
// index.html
let xhr = new XMLHttpRequest()
document.cookie = 'name=xiamen' // cookie不能跨域
xhr.withCredentials = true // 前端设置是否带cookie
xhr.open('PUT', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'xiamen')
xhr.onreadystatechange = function() {if (xhr.readyState === 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {console.log(xhr.response)//得到响应头,后台需设置Access-Control-Expose-Headersconsole.log(xhr.getResponseHeader('name'))}}
}
xhr.send()
6,基于post message实现跨域处理
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
7,用 document.domain + iframe实现跨域
只能实现同一个主域,不同子域之间的操作.
v.qq.com
和sports.qq.com
是在同一个主域。
父页面Ahttp://www.zhufengpeixun.cn/A.html
<iframe src="http://school.zhufengpeixun.cn/B.html"></iframe>
<script>document.domain = 'zhufengpeixun.cn';var user = 'admin';
</script>
子页面Bhttp://school.zhufengpeixun.cn/B.html
<script>document.domain = 'zhufengpeixun.cn';alert(window.parent.user);
</script>
8,location.hash + iframe跨域
9,window.name + iframe跨域
参考
参考
参考