码字仓颉

跨域及常见解决方案

2017-10-16

跨域及常见解决方案

本文主要讲解什么是跨域以及跨域的解决方案

一、什么是跨域

  • 同源策略
    为了安全起见,浏览器必须保证只有 协议+域名+端口 一模一样才允许发 AJAX 请求。

二、跨域的解决方案

跨域的解决方案很多,最常见的解决方案有:CORSJSONP代理转发WebSocket

1、CORS

CORS 是当前适用场景最广泛,也是最新、最受推崇的一种跨域解决方案。主要操作就是在服务端的响应头中设置 Access-Control-Allow-Origin

2、JSONP

JSOP 是一种传统的跨域解决方案,有一定局限性(仅支持 GET 请求),目前很少使用。

  • 首先,有些 html 标签(form、a、img、link、script)可以实现 get 请求,例如:
1
2
3
4
5
6
7
8
9
10
11
12
button.addEventListener('click', e => {
let image = document.createElement('img')
image.src = '/pay'
image.onload = function() {
// 状态码是 200~299 则表示成功
alert('成功')
}
image.onerror = function() {
// 状态码大于等于 400 则表示失败
alert('失败')
}
})
  • JSONP 基本原理:

    1. 请求方创建 script,src 指向响应方,同时传一个查询参数 ?callbackName=yyy
    2. 响应方根据查询参数 callbackName,构造形如这样的响应
      • “yyy.call(undefined, data)”
      • “yyy(data)”
    3. 浏览器接收到响应,就会执行 yyy.call(undefined, data)
    4. 那么请求方就知道了他要的数据
  • 下面是实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
button.addEventListener('click', e => {
let functionName = 'frank' + parseInt(Math.random() * 10000000, 10)
window[functionName] = function() {
// 每次请求之前搞出一个随机的函数
amount.innerText = amount.innerText - 0 - 1
}
let script = document.createElement('script')
script.src = '/pay?callback=' + functionName
document.body.appendChild(script)
script.onload = function(e) {
// 状态码是 200~299 则表示成功
e.currentTarget.remove()
delete window[functionName] // 请求完了就干掉这个随机函数
}
script.onerror = function(e) {
// 状态码大于等于 400 则表示失败
e.currentTarget.remove()
delete window[functionName] // 请求完了就干掉这个随机函数
}
})
//后端代码
if (path === '/pay') {
let callbackName = query.callback
response.setHeader('Content-Type', 'application/javascript')
response.write(`
${callbackName}.call(undefined, 'success')
`)
response.end()
}

3、使用 Nginx(或其它服务器)转发

  • 使用 Nginx(或其它服务器)将请求代理转发可以很方便解决跨域问题。它的原理也是基于CORS的。首先,我们应该明白,出现跨域问题是浏览器的安全机制引起的,浏览器拒绝请求非同源 URL。对于服务端程序是没有这个限制的。所以我们可以通过一个轻便的服务器(常用 Nginx)对非同源请求进行转发,转发的同时设置响应头 Access-Control-Allow-Origin 的值为 * 或者目标地址,这样浏览器直接请求 Nginx 代理转发后的 URL 地址就不会出现跨域报错。
  • 使用这种方式解决跨域问题的一个优势就是不需要更改服务端代码,缺点就是增加了网络请求的复杂度和流程,会影响性能,也增加了系统部署复杂度。
  • 下面给出 Nginx 反向代理的一个简单配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 8091;
server_name localhost;
location / {
# 没有配置OPTIONS的话,浏览器如果是自动识别协议(http or https),那么浏览器的自动OPTIONS请求会返回不能跨域
if ( $request_method = OPTIONS ) {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'Access-Control-Allow-Methods' "POST, GET, PUT, OPTIONS, DELETE";
add_header 'Access-Control-Max-Age' "3600";
add_header 'Access-Control-Allow-Headers' "Origin, X-Requested-With, Content-Type, Accept, Authorization";
add_header 'Access-Control-Allow-Credentials' "true";
add_header 'Content-Length' 0;
add_header 'Content-Type' text/plain;
return 200;
}
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type,*';
proxy_pass http://127.0.0.1:8888;
}
}

上面配置中,$http_origin 是 nginx 中的一个默认变量,其值为请求方的地址。上述配置中,实际的服务地址为 http://127.0.0.1:8888,转发后的地址为 http://127.0.0.1:8091,前端直接请求 http://127.0.0.1:8091 即可。下面为请求结果:

4、WebSocket

5、Chrome 插件:Allow-Control-Allow-Origin: *

该插件可谓是前端开发利器,可帮助我们在开发中临时解决跨域问题。
该插件实现跨域的机制是:利用 CHrome 浏览器开发接口,拦截了每次的 HTTP 请求,并修改了请求头和响应头信息,主要是添加了 Access-Control-Allow-Origin: *。基本原理是

  • 欺骗浏览器:在 Option 预请求中,将响应头中的Access-Control-Allow-Origin篡改为你发请求的客户端地址,这样就可以通过浏览器的校验;
  • 欺骗服务器:在真实请求中,将请求头中的 Origin 修改为服务器允许的地址,这样就可以通过服务端的校验;

关于该插件的详细机制和原理可以查看这里。类似改功能的插件还有几个,可以自己在谷歌应用点查找。

参考

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章