本文主要总结了5中常用的跨域方法,包括JSONP、CORS、Nginx、Proxy与WebSocket。在日常练手的小项目中,推荐CORS,比较方便易理解。(部分图片来源网络,如有侵权,请联系删除)
所谓同源是指:域名、协议、端口相同。核心就在于它认为自任何站点装载的信赖内容是不安全的。浏览器处于安全方面的考虑,只允许本域名下的接口交互, 不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源。常用数据传输的方式有ajax与fetch。同源策略又分为以下两种:
DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe (iframe 元素会创建包含另外一个文档的内联框架(即行内框架))跨域的情况,不同域名的 iframe 是限制互相访问的。
XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。
基于同源策略,限制了以下行为:
Cookie、LocalStorage 和 IndexDB 无法读取
DOM 和 JS 对象无法获取
Ajax请求发送不出去
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。判断是否存在跨域问题很简单,这里就不进行赘述了。主要解决方法如下:
JSONP是一段参数是json格式(大多数情况)的JS代码。Web页面上调用js文件时不受跨域影响(不仅如此,别人还发现凡是拥有”src”这个属性的标签都拥有跨域的能力,比如script、img、iframe)。工作原理如下:
//客户端
$.ajax({
url:'http://127.0.0.1:8001/list',
method:'get',
dataType:'jsonp',
success: res=>{
console.log(res)
}
});
//服务端
let express = require('express'),
app = express();
app.listen(8001, _ =>{ // ’_‘ 主要是为了占位
console.log('ok!')
});
app.get('/list',(req,res) => {
let {
callback = Function.prototype //设置默认值为一个空函数
} = req.query;
let data = {
code:0,
message:'test'
};
res.send(`${callback}(${JSON.stringify(data) // 转义数据, 并发送给回调全局函数中
})`);
});
优点:不受同源策略的影响 ,兼容性更好,在古老的浏览器中皆可运行,不需要XMLHttpRequest或ActiveX的支持,并且在请求完成后可以通过调用callback的方式回传结果
缺点:只支持GET请求而不支持POST、PUT、DELETE等其他类型的HTTP请求。只支持跨域HTTP请求,不能解决不同域的两个页面之间如何进行JS调用的问题。有可能存在URL劫持问题,返回木马文件,即XSS攻击
CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。(PS: CORS同时需要后端与浏览器的支持,只要后端实现了CORS,就实现了跨域)
注意:CORS分为简单请求与复杂请求,简单请求需满足以下条件:
1: 使用下列方法之一:GET、HEAD、POST
2:Content -Type的值仅限于下列三者之一: text/plain、multipart/form-data、application/x-www-form-urlencoded
复杂请求:
不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。
1:基于Node.js的跨域设置
// 1. 客户端发送ajxa/fetch请求,首先进行axios默认配置
fetch( "http://目标资源网站", {methods:'GET'})
.then(response => response.json())
.then(data => console.log(data))
//2:服务器设置相关信息(需要处理options试探性请求--复杂请求)
app.use((req,res,next)=>{
//允许哪一个源获取数据
res.header("Access-Control-Allow-Origin","http://loaclhost:8000");
//允许的客户段请求头,可以获取哪些数据
res.header("Access-Control-Allow-Credentials","Content-Type,Content,Length,Authorization , Accept,X-Requested-Width");
// 允许携带cookie
res.setHeader('Access-Control-Allow-Credentials', true)
//运行客户端的请求方式,预检请求,查看客户端是否存在恶意方法methods等来操控数据
res.header("Access-Control-Alloe-Methods","PUT,POST,GET,DELETE.HEAD.OPTIONS");
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// 允许返回的头
res.setHeader('Access-Control-Expose-Headers', 'name')
if(req.methods==='OPTIONS'){
res.send('ok!');
return;
}
next();
})
2:基于Python的CORS跨域实现:
def after_request(response):
# JS前端跨域支持,使用CORS方式
response.headers['Cache-Control'] = 'no-cache'
response.headers['Access-Control-Allow-Origin'] = '*'
return response
更详细的可以看阮一峰大佬的博客:跨域资源共享 CORS 详解 - 阮一峰的网络日志https://www.ruanyifeng.com/blog/2016/04/cors.html
JSONP与CORS的对比:
1: JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
2: 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
3: JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)
Nginx 实现跨域请和proxy原理一致,只是Nginx帮我们做了服务器转发请求,我们在请求数据时,仍然写自己的网站的请求地址,而不是实际的请求网站的地址。
流程如下:
1:将自己的前端资源部署在Nginx的HTTP服务器
2:设置网站资源根目录
3:在 nginx.conf 配置文件中,设置监听端口与proxy_pass的转发规则
前端通过代理访问数据
server{
listen 2222;
server_name localhost; // 设置监听端口为http://localhost:2222
location {
root /Users/relax/Desktop/9种跨域/nginx反向代理处理跨域/html; //# 配置自己的静态网站路径,并指定网站的根目录
index index.html index.htm;
}
location /api{ //转发规则设置,也可使用正则表达式
proxy_pass http://127.0.0.1:3000
add_header Access-Control-Allow-Origin *;
}
}
CORS和JSONP解决跨域问题都有一个前提:需要后台的支持,Proxy可以解决这一问题。一般配合webpack和webpack-dev-server使用,具体流程如下:
自己的网站端口号是: http://127.0.0.1:12345
自己网站后台有一个 /proxy
的路由专门处理跨域数据请求.
同时,有网站提供了一个数据接口,但是端口号是:54321
-> http://127.0.0.1:54321/data.json
,因为端口号不同,而产生了跨域问题
在请求data.json
时,使用/proxy
路由进行代理=> http://127.0.0.1:12345/proxy?http://127.0.0.1:54321/data.json
经自己的服务器解析路径获取到真是的数据连接 http://127.0.0.1:54321/data.json
自己的服务器由于没有同源策略的限制,所以可以直接发送这个请求,并或者数据返回值.
最后经由自己的服务器返回给自己的前端浏览器
VUE中的跨域设置
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://127.0.0.1:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些https服务报错时用
cookieDomainRewrite: '127.0.0.1' // 可以为false,表示不修改
}],
noInfo: true
}
}
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 「WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据」。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
// 客户端
// 服务端
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('我不爱你')
});
})