目录

1. 同源策略

跨域问题其实就是浏览器的同源策略造成的。

同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是:协议端口号域名必须一致。

下表给出了与 URL ​​http://store.company.com/dir/page.html​​ 的源进行对比的示例:

URL

是否跨域

原因

​http://store.company.com/dir/page.html​

同源

完全相同

​http://store.company.com/dir/inner/another.html​

同源

只有路径不同

​https://store.company.com/secure.html​

跨域

协议不同

​http://store.company.com:81/dir/etc.html​

跨域

端口不同 ( http:// 默认端口是80)

​http://news.company.com/dir/other.html​

跨域

主机不同

同源策略:protocol(协议)、domain(域名)、port(端口)三者必须一致。

2. 如何解决跨域?

很多情况下,我们前后端资源可能不在一台服务器上,那就需要进行跨域请求。

下面看一下解决跨域的方式有哪些:

2.1 CORS

2.1.1 CORS定义

下面是MDN对于CORS的定义:

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain)上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域HTTP 请求。

CORS需要浏览器和服务器同时支持,整个CORS过程都是浏览器完成的,无需用户参与。因此实现CORS的关键就是服务器,只要服务器实现了CORS请求,就可以跨源通信了。

2.1.2 CORS请求分类

浏览器将CORS分为简单请求非简单请求

简单请求不会触发CORS预检请求。若该请求满足以下两个条件,就可以看作是简单请求:
1)请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

2)HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

若不满足以上条件,就属于非简单请求了。

2.1.3 简单请求和非简单请求的过程

(1)简单请求过程:

对于简单请求,浏览器会直接发出CORS请求,它会在请求的头信息中增加一个Orign字段,该字段用来说明本次请求来自哪个源(协议+端口+域名),服务器会根据这个值来决定是否同意这次请求。

如果Orign指定的域名在许可范围之内,服务器返回的响应就会多出以下信息头:

Access-Control-Allow-Origin: http://api.bob.com  // 和Orign一直
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值
Content-Type: text/html; charset=utf-8 // 表示文档类型

如果Orign指定的域名不在许可范围之内,服务器会返回一个正常的HTTP回应,浏览器发现没有上面的Access-Control-Allow-Origin头部信息,就知道出错了。这个错误无法通过状态码识别,因为返回的状态码可能是200。

在简单请求中,在服务器内,至少需要设置字段:Access-Control-Allow-Origin

(2)非简单请求过程

非简单请求是对服务器有特殊要求的请求,比如请求方法为DELETE或者PUT等

非简单请求的CORS请求会在正式通信之前进行一次HTTP查询请求,称为预检请求

浏览器会询问服务器,当前所在的网页是否在服务器允许访问的范围内,以及可以使用哪些HTTP请求方式和头信息字段,只有得到肯定的回复,才会进行正式的HTTP请求,否则就会报错。

预检请求使用的请求方法是OPTIONS,表示这个请求是来询问的。他的头信息中的关键字段是Orign,表示请求来自哪个源。除此之外,头信息中还包括两个字段:

  • Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法。
  • Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。

服务器在收到浏览器的预检请求之后,会根据头信息的三个字段来进行判断,如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是允许跨域请求,如果没有,就是不同意这个预检请求,就会报错。

服务器回应的CORS的字段如下:

Access-Control-Allow-Origin: http://api.bob.com  // 允许跨域的源地址
Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header // 服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Max-Age: 1728000 // 用来指定本次预检请求的有效期,单位为秒

只要服务器通过了预检请求,在以后每次的CORS请求都会自带一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

在非简单请求中,至少需要设置以下字段:

'Access-Control-Allow-Origin'  
'Access-Control-Allow-Methods'
'Access-Control-Allow-Headers'
2.1.4 减少OPTIONS请求次数

OPTIONS请求次数过多就会损耗页面加载的性能,降低用户体验度。

所以尽量要减少OPTIONS请求次数,可以后端在请求的返回头部添加:Access-Control-Max-Age:number。它表示预检请求的返回结果可以被缓存多久,单位是秒。

该字段只对完全一样的URL的缓存设置生效,所以设置了缓存时间,在这个时间范围内,再次发送请求就不需要进行预检请求了。

2.1.5 CORS中Cookie相关问题

在CORS请求中,如果想要传递Cookie,就要满足以下三个条件:

(1)在请求中设置 withCredentials

默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie.

// 原生 xml 的设置方式
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// axios 设置方式
axios.defaults.withCredentials = true;

(2)Access-Control-Allow-Credentials 设置为 true

(3)Access-Control-Allow-Origin 设置为为非 *

2.2 JSONP

JSONP跨域主要是利用​​<script>​​标签没有跨域限制这个特性来完成的。但是,该方法只支持GET请求。

JSONP由两部分组成,回调函数和数据。回调函数就是在响应到来时在页面中调用的函数。而数据就是传入回调函数中的JSON数据。

JSONP的流程如下:

  • 声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。
  • 创建一个​​<script>​​标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)。
  • 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是show(‘hello’)。
  • 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。

客户端的代码:

// index.html
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[callback] = function(data) {
resolve(data)
document.body.removeChild(script)
}
params = { ...params, callback } // wd=b&callback=show
let arrs = []
for (let key in params) {
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join('&')}`
document.body.appendChild(script)
})
}
jsonp({
url: 'http://localhost:3000/say',
params: { wd: 'hello world' },
callback: 'show'
}).then(data => {
console.log(data)
})

服务端的代码:

// server.js
let express = require('express')
let app = express()
app.get('/say', function(req, res) {
let { wd, callback } = req.query
console.log(wd) // hello world
console.log(callback) // show
res.end(`${callback}('hello')`)
})
app.listen(3000)

上面这段代码相当于向​​http://localhost:3000/say?wd=hello world&callback=show​​​这个地址请求数据,然后后台返回​​show('hello')​​​,最后会运行​​show()​​​这个函数,打印出​​'helloworld'​​。

JSONP的缺点:

  • 具有局限性, 仅支持get方法
  • 不安全,可能会遭受XSS攻击

2.3 代理跨域

上面的两种跨域解决方法,都需要前后端的配合,才能完成跨域。而下面要说的代理跨域只需要前端配置就可以进行跨域请求。

代理跨域的原理就是,利用服务端的请求不会跨域的特性,即服务器向服务器发送请求不会出现跨域。

代理跨域的步骤如下:

  • 代理服务器接收客户端的请求
  • 代理服务器将请求转发给真正的服务器
  • 服务器将数据返回给代理服务器
  • 代理服务器将响应数据转发给客户端
  • 前端跨域解决方案_服务器

在Vue框架中,会利用node + webpack + webpack-dev-server 代理接口跨域。在开发环境下,由于 Vue 渲染服务和接口代理服务都是 webpack-dev-server,所以页面与代理接口之间不再跨域,无须设置 Headers 跨域信息了,在​​vue.config.js​​中进行如下设置:

module.exports = {
dev: {
//...
proxyTable: {
'/api': {
target: 'http://10.0.100.7:8081', //设置调用接口域名和端口号
changeOrigin: true,
pathRewrite:{
"^/api":""
}
}
},
}

用axios调用接口时:

this.axios.get('/api/user'
}).then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
})