老生常谈
一 前置认知
域名的组成
同源:协议、域名、端口三者一致即为同源,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略:一种约定,它是浏览器最核心也最基本的安全功能(非Http协议),如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。
跨域:协议、域名、端口三者之中任意一个不同,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
同源策略的最常见限制:axios请求发出后,结果被浏览器拦截了
ps: img、link、script标签允许跨域加载资源
二 一些跨域解决方案
Jsonp
上古时期的老方案了
主要是利用script标签没有跨域限制的特性,从其他来源动态获得JSON数据,Jsonp兼容性好,但是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
实现过程
- 声明一个回调函数,将其函数名当作参数值,传递给跨域请求数据的服务器
- 创建一个
<script>
标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名 - 服务器接收到请求后,将传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是
show('I love you')
- 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作
CORS跨域资源共享
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
CORS 规定了三种不同的交互模式
- 简单请求
- 需要预检的请求
- 附带身份凭证的请求
这三种模式从上到下层层递进,请求可以做的事越来越多,要求也越来越严格。
简单请求
简单请求的前置条件
- 请求方法属于get、post、head
- 请求头仅包含安全的字段(Accept、Content。。。)
- 请求头如果包含
Content-Type
,仅限下面的值之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
需要预检的请求
流程
- 浏览器发送预检请求,询问服务器是否允许
- 服务器允许
- 浏览器发送真实请求
- 服务器完成真实的响应
例如一个预检请求
预检请求有以下特征:
-
请求方法为
OPTIONS
-
没有请求体
-
请求头中包含
Origin
:请求的源,和简单请求的含义一致Access-Control-Request-Method
:后续的真实请求将使用的请求方法Access-Control-Request-Headers
:后续的真实请求会改动的请求头
预检成功后,服务器回回应对应的预检响应,然后在浏览器会发送真实的数据请求,服务器进行回应(很符合http请求的特点)
附带身份凭证的请求
默认情况下,ajax 的跨域请求并不会附带 cookie 不过设置XMLHttpRequest中的withCredentials为true就可以让该请求就是一个附带身份凭证的请求
代理模式
目前前端主流框架在开发模式下解决跨域的最常见方案。 可分为:
- Nginx
反向代理 (同源策略仅限于浏览器,服务器无限制)
## 假如页面a在www.198.com, 而服务器在www.199.com, 因为同源策略的限制,页面a发起的请求会跨域,但是通过nginx配置一个代理服务器,域名和a页面相同,都是www.198.com,反向代理访问www.199.com的接口
# Nginx代理服务器
server {
listen 80;
server_name www.198.com;
location / {
# 反向代理地址
proxy_pass http://www.199.com:97;
# 修改Cookie中域名
proxy_cookie_domain www.198.com www.199.com;
index index.html index.htm;
# 前端跨域携带了Cookie,所以Allow-Origin配置不可为*
add_header Access-Control-Allow-Origin http://www.hahaha.com;
add_header Access-Control-Allow-Credentials true;
}
}
-
Proxy
例如Vue-CLI 是基于
webpack
的,通过webpack-dev-server
在本地启动脚手架,也就是在本地启动了一个Node
服务,来实时监听和打包编译静态资源,由于都是封装好的,只需要配置即可,我们在vue.config.js
中配置代理如下,写法很多,列几个常见的自行选择
module.exports = {
//...
devServer: {
proxy: {
'/api': 'http://www.199.com'
}
}
}