什么是跨域?如何解决?
# 跨域
- 什么是跨域
- 跨域常见的解决方案
- JSONP
- CORS
- 搭建代理服务器
# 什么是跨域
所谓跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,其实就是浏览器为用户施加的一种安全限制。
什么叫做同源:
所谓同源,指的是域名、协议名、端口均相同。如果有一个不同,浏览器都会认为是跨域
http://www.123.com/index.html 调用 http://www.123.com/server.js(非跨域)
http://www.123.com/index.html 调用 http://www.456.com/server.js(域名不同,所以跨域)
http://abc.123.com/index.html 调用 http://def.123.com/server.js(子域名不同,所以也是跨域)
http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.js(端口不同,所以也是跨域)
http://www.123.com/index.html 调用 https://www.123.com/server.js(因为协议不同,所以也跨域了)
2
3
4
5
注意:localhost 和 127.0.0.1 虽然都指向本机,但是也是属于跨域
# 同源策略简介
同源策略(Same origin policy)是一种约定,是浏览器最核心,也是最基本的安全策略。它由网景公司(Netscape)提出,现已成为国际标准。
所有支持 JavaScript 语言的浏览器,都支持同源策略。
该策略是指,一个“源”中的 JavaScript 代码,只能读取到和该“源”同源的文档资源。
比方说,有一个页面,地址是http://weibo.com/
,该页面中有一段 JavaScript 代码,想要去读取 http://sina.com/
页面中的内容,由于两个地址不同源,这样做就违背了同源策略,是不允许的!
为什么会有这样的策略?同源政策的目的,主要是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A 网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取 A 网站的 Cookie,会发生什么?
很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,“同源政策”是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。
随着互联网的发展,“同源政策”越来越严格。目前,如果非同源,共有三种行为受到限制。
Cookie、LocalStorage 和 IndexDB 无法读取。
DOM 无法获得。
Ajax 请求不能发送。
浏览器的同源策略虽然提高了网络安全性,但它阻碍了消息的流通,降低了便利性,有的时候,会给我们网站开发带来不小的困扰,合理的用途也受到影响。
举个例子,比方说你在一家互联网公司就职,这个公司有两个网站,它们拥有不同的域名:
http://www.abc.com : 公司官网,包含公司新闻、产品介绍等,会员可以注册和登录,给公司留言
http://bbs.abc.com : 公司论坛,会员可以注册和登录,发布和浏览帖子
2
可以看出,这两个网站都有会员功能,也就是说,这两个网站都会用到会员的数据。假设两个网站都有一些地方,需要用 Ajax 请求服务器,得到当前登录会员的信息,按照过去的做法,会怎么办呢?我只能在两个网站中分别编写一个页面,来提供会员的数据:
http://www.abc.com/getuser.php : 用 GET 请求该地址,可得到当前登录会员的用户信息
http://bbs.abc.com/getuser.php : 用 GET 请求该地址,可得到当前登录会员的用户信息
2
现在,两个网站各自为阵。
www.abc.com
中的页面如果要用 Ajax 得到用户信息,就要去请求http://www.abc.com/getuser.php
。
bbs.abc.com
中的页面如果要用 Ajax 得到用户信息,就要去请求http://bbs.abc.com/getuser.php
。
虽然这样做没有什么问题,但是,肯定会造成重复的代码,因为http://www.abc.com/getuser.php
和 http://bbs.abc.com/getuser.php
的功能完全一 样,代码也是完全一样的。
试想,如果公司的规模逐渐扩大,有了更多的站点,比如 10 个,而每个站点都需要用到这样的功能,那重复的代码岂不是越来越多。并且,这只是其中一个小功能,而在多站点的产品体系中,类似这样的功能非常之多,所以,在多站点的应用中,这种做法其实并不可取。
而更好的做法是什么呢?将每个站点都需要用到的功能,提取到一个单独的站点中,形成下面的结构:
http://www.abc.com : 公司官网,包含公司新闻、产品介绍等,会员可以注册和登录,给公司留言
http://bbs.abc.com : 公司论坛,会员可以注册和登录,发布和浏览帖子
http://resource.abc.com : 资源站,其他站点可以请求资源站中的相应页面,获得想要的数据
2
3
多了一个资源站,专门用于提供共同需要的数据。以后,无论是公司官网,还是公司论坛,凡是需要用 Ajax 得到登陆者信息,都可以去请求统一的地址http://resource.abc.com/getuser.php
。这样一来,就不需要为每一个站点重复的去开发这样的页面了。
比如,公司官网的http://www.abc.com/index.php
和公司论坛的http://bbs.abc.com/form.php
都需要用 Ajax 来获取信息,那么它们可以去请求统一的地址http://resource.abc.com/getuser.php
来得到。
虽然这是最理想的做法,然而,由于请求的是非同源地址,会导致 JavaScript 无法收到服务器的响应:
目前,解决跨域的方式主要有 3 种,分别是:
- JSONP
- CORS(跨域资源共享)
- 搭建代理服务器
后面,将为大家介绍如何使用这几种方式来解决跨域问题。
# JSONP 解决跨域
JSONP 是最早的解决跨域的方式。这种方式来自于民间,是民间高手们解决跨域问题的一种方式。现在虽然已经有了 CORS 这种很优雅的解决跨域的方式,JSONP就显得有点过时了,但是由于他能够在各个版本的浏览器都顺利执行,并且出于对历史的尊重,所以我们还是要介绍一下使用 JSONP 来解决跨域的方式。
# JSONP 解决跨域的思路
跨域主要来源于浏览器的同源策略。但是后来,民间高手发现,同源策略只对js代码进行限制,也就是说如果你使用js来发送请求来请求后端数据,这个时候,会收到同源策略的限制。但是我们可以使用一些其他的方式来发送请求,从而绕过同源策略的限制。
我们拥有一些东西,也会向后台发送请求,<img>
,<link>
,<script>
。
经过研究我们发现,跨域主要是对 ajax 进行限制,所以我们打算利用其他标签要和后台交互的特点,从而绕过了ajax来发送请求。在上面所列举的标签里面,都有一个 src 属性,浏览器是不会对这个 src 属性进行限制的。再继续研究,发现 script 标签要优于 img、link 这些标签。img请求回来的内容,会被当成图片,link请求回来的内容,会被当成 css,script 标签是最合适的。
(1)使用原生 js 来书写 JSONP解决跨域问题
核心思路:创建一个script标签,然后设置 src 属性,src所对应的值就是我们要请求的服务器地址。最后将这个 script 标签添加到页面上,从而发起请求。
let script = document.createElement('script');
script.setAttribute('src','http://127.0.0.1:3000/stu');
document.getElementsByTagName('head')[0].appendChild(script);
document.getElementsByTagName('head')[0].removeChild(script);
2
3
4
到目前为止,点击按钮不会再报错了,但是感觉数据没有拿到,这是因为服务器端那边代码也要做出一定的修改。
router.get('/stu', function (req, res, next) {
// 服务器端要作出的改变就是不再是单纯返回数据就完事儿了
// 而是要把你返回的数据包裹在js代码
res.send(`
console.log([
{ name: 'william', age: 18 },
{ name: 'yaoyao', age: 18 },
{ name: 'shasha', age: 23 },
{ name: 'qingqing', age: 16 }
]);
`);
})
2
3
4
5
6
7
8
9
10
11
12
(2)jQuery 中使用JSONP来解决跨域
在 jQuery中,提供了一种更加优雅的JSONP的使用方式
- 在请求的地址后面添加一个 ?callback=?,jQuery 会随机生成一个函数名赋值给callback
- 将 ajax 方法中的 dataType 设置为 jsonp
- 在服务器端接收callback的值,这是由jquery自动生成的一个函数,将要返回的数据包裹在这个函数里面
// index.html
$.ajax({
url : 'http://127.0.0.1:3000/stu?callback=?',
dataType:'jsonp',
success : function(data){
console.log(data);
}
})
// index.js
// 服务器端要做出的改变:获取 callback
const func = url.parse(req.url, true).query.callback;
// 然后将要返回的数据包裹在这个函数里面
res.send(`${func}([
{ name: 'william', age: 18 },
{ name: 'yaoyao', age: 18 },
{ name: 'shasha', age: 23 },
{ name: 'qingqing', age: 16 }
])`);
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# CORS 方法解决跨域
CORS 方法可以说是解决 ajax 跨域方案中最为优雅的,最为简单的方案。
在上面的学习中,你可能觉得跨域是请求没有发送出去。
但是实际上的情况是,请求正常发送到了服务器,服务器也接收到了你的请求正常给你返回了响应。只不过由于浏览器的同源策略,导致不会将返回的数据交给 js 来执行。
在国际标准组织中,为HTTP新增了一个标准,这个标准就称之为 CORS,翻译成中文就是“跨域资源共享标准”。
这是一个国际标准,目前大多数浏览器已经支持(小部分浏览器(主要是IE)还不支持)
要使用 CORS,非常简单,只需要在响应头里面添加一些键值对即可。
具体的操作如下:
// app.js
// 创建一个中间件,这个中间件的作用是允许跨域
const allowCorssDomain = function(req,res,next){
// 我们在这个中间件里面主要是设置响应头
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With,Origin,Content-Type,Accept");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
res.header('Access-Control-Allow-Credentials','true');
next();
}
app.use(allowCorssDomain);
2
3
4
5
6
7
8
9
10
11
12
13
# 使用代理服务器来解决跨域
跨域问题的出现,归根结底,是浏览器的安全策略的限制。但是如果是服务器向服务器发送请求,就不会有这样安全限制。所以我们现在就可以曲线救国,先让 ajax 请求同源的服务器,请求到达同源服务器(中间服务器)之后,再由同源服务器向数据服务器(目标服务器)发送请求。
具体操作
首先第一步,首先我们需要再创建一个服务器。
代理服务器也是使用express proxy-server
来创建的。
我们现在知道,代理服务器要对请求进行转发,转发给目标服务器。
这个时候,就需要对代理服务进行一个配置,不然请求是不会转发的。
这个时候,我们需要一个中间件,http-proxy-middleware,这个是一个第三方中间件,这个中间件的作用就是对请求进行一个转发。
接下来来安装中间件:
npm i http-proxy-middleware
在代理服务器安装好 http-proxy-middleware 中间件之后,接下来需要先配置这个中间件,然后注册。
配置主要两个东西:1. 你要转发,你转发到哪儿去 2. 哪些请求要转发,哪些请求不转发
具体的配置如下:
// 对中间件进行一个配置
const options = {
target : 'http://127.0.0.1:3000', // 你要转发,你转发到哪儿去
changeOrigin : true , // 是否可以改写你的 url
pathRewrite : {
// 定义你的改写规则
'^/api' : '/'
// 一会儿我们的代理服务器发送请求的时候 我们会写成这个样子 /api/stu
// 完整的其实就是 http://localhost:3001/api/stu
// 改成出来就是:http://127.0.0.1:3000/stu
}
}
2
3
4
5
6
7
8
9
10
11
12
接下来对这个中间件进行注册
// 在这个位置,代表的是url 以api开头的,要用 proxy 这个中间来进行处理
// http://localhost:3001/api/stu
app.use('/api',proxy.createProxyMiddleware(options));
2
3
之后在客户端发送请求的时候,请求就要添加 api 开头,如下:
$.ajax({
url : '/api/stu', // 添加 api 这个前缀,代表该请求要转发
type : 'get',
success : function(data){
console.log(data);
}
})
2
3
4
5
6
7