什么是跨域?
- 广义跨域
- 资源跳转:表单提交
- 资源嵌入:link、script、img、frame标签,CSS中的background:url()、@font-face()文件外链
- 脚本请求:JS发起的ajax请求
- 狭义跨域:浏览器的同源策略限制
背景
我们在本文讨论的是狭义的跨域概念
为了安全起见,浏览器对页面的资源访问进行了限制,A页面想获得B页面的资源,如果A页面和B页面的协议,域名,端口均相同,则跨域正常操作;一旦有一项不同,就涉及跨域访问。所以跨域问题的出现,其实是由于浏览器的同源策略限制导致的
同源策略
协议 + 域名 + 端口 相同
解决跨域问题方案
- 通过jsonp跨域
- document.domain + iframe跨域
- location.hash + iframe
- window.name + iframe跨域
- postMessage跨域
- 跨域资源共享(CORS)
- nginx代理跨域
- nodejs中间件代理跨域
- WebSocket协议跨域
下面给出各方案的特点,使用方法可以参考使用详情
jsonp解决跨域 —— 前端与后端配合
- 前端动态创建script标签,将请求地址放到src中
- 后端调用回调函数
弊端:只支持get请求
document.domain + iframe跨域 —— 适用于主域相同,子域不同的情况
- 两个页面通过JS强制设置document.domain为主域,实现同域
PS:子域不同不适用原因:因为设置document.domain只支持设置为本身或者是基础域名(suffix后缀域名)
location.hash + iframe跨域 —— 适用于跨域通信
- a与b不同域,借助a的同域c来和b实现跨域通信。a的iframe嵌套b,b的iframe嵌套c
- 不同域之间,a与b,b与c,通过location.hash单向通信,a修改b的src,b修改c的src
- 同域之间,a与c,c直接通过parent.parent.callback访问a的回调函数
window.name + iframe跨域 —— 跨域请求数据
- 在iframe加载完外域之后,修改src由外域转为本域
- 在外域修改window.name,在本域可以读取到,实现跨域
PS:window.name的特别之处,name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)
postMessage跨域 —— 不同窗口,嵌套iframe之间的消息传递,数据传递
- 访问window.postMessage属性,指定域,传递数据
- 在window设置监听器
CORS —— 服务器设置Access-Control-Allow-Origin
- 同源策略限制,浏览器所在cookie为跨域请求接口所在域的cookie,而非当前页的cookie
- 服务器设置Access-Control-Allow-Origin
nginx反向代理接口跨域
- 通过nginx在前端服务器配置一个代理服务器,端口不同 —— 请求nginx转发到前端服务器
- 修改cookie中的domain信息,方便带上domain1的cookie发起请求
Ps:https://www.cnblogs.com/renjing/p/6394725.html
webSocket实现跨域
- 前端使用Socket.io,封装了webSocket接口
- Node后台也需要引入socket.io
总结
所以禁止跨域,实际上是浏览器的安全行为,上述的大多数方案都是需要服务器做相应的处理去配合前端解决跨域问题。那如果有需求是目标后端服务器不能做修改,那么除了nginx的其他方法都不能实现。
下面着重讲一下nginx的方法如何解决跨域问题
nginx反向代理服务器
nginx介绍
nginx是web服务器,功能包括:
- 处理静态文件,索引文件以及自动索引;
- 反向代理加速(无缓存),简单的负载均衡和容错;
解决原理
在解决跨域问题上,将前端项目部署在前端服务器上,在前端服务器上搭建一个nginx服务器。现在修改nginx配置,实现由页面请求本域名的一个地址,nginx监听这个地址,拦截请求,若请求静态资源,直接返回资源文件;若请求目标服务器,由nginx服务器转发该请求到目标服务器,nginx服务器接收处理后的结果,将处理后的结果返回给页面,实现跨域。
因为狭义的跨域问题是因为浏览器的同源策略,如果将请求交给nginx服务器去完成,就不会出现跨域问题了。
反向代理:由于页面访问的是nginx服务器,通过将不同的请求转发到前端项目服务器或者是目标后端服务器,起到的是代理服务器的作用,所以是反向代理。(反向代理:代理服务器)
配置文件
下面阐述几个这里配置文件中,比较关键的几个字段,仅供参考。详细配置还是建议查看官方
- listen:监听端口号,nginx运行的端口号
- server_name:虚拟服务器识别路径。不同的域名的请求,会通过request header中的host字段,匹配到特定的server模块,将请求转发到对应的服务器中。针对某个请求的host,多个server块匹配的顺序参考 nginx配置:server_name的作用
location:页面定位规则,解决跨域问题重点配置
配置nginx.conf,修改配置文件中的server模块,如下图:
一个server代表启动一个服务,对应的location是定位规则。我们可以看出来,nginx服务器是通过location的配置来路由的,所以我需要修改location的配置。
实现跨域
假设现在,我们的前端项目是 ‘localhost:3000’,目标后端服务器是另一个局域网IP,这里我们假设为 ‘192.168.2.233:8080’。其中前端项目的localhost:3000/html/msg.html需要请求 ‘http://192.168.2.233:8080/api/?name=ptp'
对应的ajax请求为:
1 | var url = 'http://192.168.2.233:8080/api/?name=ptp' |
上述的请求会遇到跨域问题,我们需要通过nginx去请求目标后端服务器。分为两个步骤
- 配置location,实现静态资源请求转向前端项目服务器
- 配置location,实现请求目标后端服务器,转向后端服务器,将结果通过nginx返回给页面
针对前端项目资源请求的跳转,配置如下:
1 | location / { |
解释如下:这边理解很简单,当访问nginx服务器,路径为 ‘/‘ 的时候,请求转发到前端项目
针对请求后端服务器的请求跳转,配置如下:
1 | location /apis { # 添加访问目录为/apis的代理配置 |
解释如下:
- location后面跟的是匹配规则,用于拦截请求,这里匹配任何以 ‘/apis’ 开头的路径
- rewrite表示重写请求路径,只对路径重写,不包括参数,rewrite的语法格式为:’rewrite
[flag];’; - 这里的 ‘^/apis/(.*)$ /$1 break’ 表示,匹配所有 ‘/apis/‘ 开头的路径,并且将用$1替换匹配到的字段,$1表示
中的第一个()中包含的内容,也就是 ‘(.*)’。整个配置的意思就是去掉路径中的 ‘/apis/‘
此时针对上述的场景,ajax请求的链接需要修改为相对地址,以达到请求nginx服务器的效果
1 | // var url = 'http://www.b.com/api/msg?name=ptp' |
这里我们的nginx服务器,因为搭建在前端项目,也就是我本人的本地,所以server_name是localhost
所以这样的配置,在页面访问nginx服务器的时候,如果访问路径 ‘/‘,也就是直接访问 ‘localhost’,nginx服务器会访问前端项目资源,做一个静态资源的返回;
如果访问路径 ‘/apis’,在浏览器看来是请求 ‘localhost/apis’,会被nginx拦截下来,然后转向目标后端服务器,将后端服务器返回的结果返回给页面,实现接口调用的效果。
注意
这里解释一下location中,proxy_pass字段最后带不带/的效果区别
带
1 | location /apis { # 添加访问目录为/apis的代理配置 |
最后带/的情况,如果访问 ‘localhost/apis/msg?name=ptp’,会被nginx服务器将请求路径转为 ‘http://192.168.2.233:8080/apis/msg?name=ptp'
带上了location后的 ‘/apis’
不带
1 | location /apis { # 添加访问目录为/apis的代理配置 |
不带/的话,如果访问 ‘localhost/apis/msg?name=ptp’,会被nginx服务器将请求路径转为 ‘http://192.168.2.233:8080/msg?name=ptp'
所以这里我们需要加上/