产生跨域的原因
- 浏览器限制
- 请求链接跨域
- 请求类型是XMLHttpRequest
跨域解决方法之一:JSONP
实现jsonp,需要前后端都需要改造,后端需要增加支持返回js函数,主要约定是url的key字段,由于jquery默认的jsonp请求的关键字key默认是callback,后端一般都会使用callback关键字,当然也可以定义另一个名称,而这个key对应的value使用什么都可以,jquery是使用随机数,后台会获取这个随机数,作为返回js函数的函数名。这是大多数情况,原理是如此,但可能有所不同。
除此之外,还需要注意要使用随机数,不然jsonp由于是js脚本,如果请求参数相同,就会使用缓存的结果,而不会使用最新的结果,jquery的jsonp默认有随机数参数
jsonp有以下弊端:服务端也需要改造,请求只能是get。由于请求是get,一般只能用来请求一些获取数据的接口,而不能用于修改用户信息等类型的接口(需要用到post)。由于这个缺点,一般很少接口会用到jsonp,解决跨域问题还需要找其他解决办法
网络请求的路径和解决思路
一般请求路径是:Client(调用方) - Apache/Nginx(http服务器) - Tomcat(被调用方后台应用服务器)。其中Apache/Nginx会处理来自client的请求,判断该请求为什么类型的请求,如果是静态请求,则直接处理返回给client,如果是动态请求,则转发给后台的应用服务器,处理完毕后则原路返回。
动态请求指,跟用户数据有关的请求,xhr等
静态请求指,跟用户数据无关的请求,例如img,js,css
解决跨域的思路有两种,一种是在被调用方后台应用服务器上做处理,在响应头上增加字段,告诉浏览器允许对方调用,浏览器就不会报跨域问题;第二种是隐藏跨域的思路,在http服务器上做处理,做一个http请求转发,浏览器发现所有的请求都是同一个域,就不会报跨域问题。
被调用方解决 - 支持跨域(应用服务器配置响应头、Apache/Nginx服务器配置响应头CORS)
当浏览器进行跨域请求时,会在预检请求的时候在请求头加上Origin字段,对应的值是当前域的信息。然后返回的时候会检查响应头里有没有允许跨域的字段,如果没有,浏览器就会报错拦截。具体检查的响应头字段是Access-Control-Allow-Origin
,它是html5新增的一项标准,IE10以下是不支持的。如果需要IE10以下都完美支持跨域,请使用http服务器转发(http反向代理)解决问题。
一般这个方法,后台应用服务器简称为filter,添加filter组件后,再在响应头里添加Access-Control-Allow-Origin
(允许跨域的域名),Access-Control-Allow-Methods
(允许跨域的请求类型)
1 | // 后台设定返回 |
简单请求和复杂请求(跨域时才区分)
浏览器在进行跨域请求时,会判断该请求是简单请求还是非简单请求,如果是简单请求,则会先执行,后判断;如果是非简单请求,浏览器就会先发一个预检命令(OPTIONS),通过之后再发跨域请求。
工作中比较常见的【简单请求】:
方法为GET、HEAD、POST
请求header里无自定义请求
Content-Type
为以下几种:- text/plain(表单数据以纯文本形式进行编码)
- multipart/form-data(一般上传文件时会用到)
- application/x-www-form-urlencoded(表单数据编码为键值对,&分隔)
工作中比较常见的【非简单请求】:
- put、delete方法的ajax请求
- 发送json格式的ajax请求(
Content-Type
为appliation/json;charset=utf-8) - 带自定义头的ajax请求
发送json格式的ajax请求时,预检命令,请求头会加上一个Access-Control-Allow-Headers
为Content-Type,询问后台应用服务器是否允许这个头。如果后台没有在filter里加上这个响应头告诉浏览器允许这个header,浏览器发现没有通过的信息就会报错
1 | // 后台设定返回 |
非简单请求一共会发送两次请求,第一次是OPTIONS预检命令,第二次才是真正的跨域请求
OPTIONS预检命令是可以做缓存的,由于非简单请求每次都会发送两次请求,这样会比较影响性能。后台应用服务器可以通过在响应头里增加Access-Control-Max-Age
,值是毫秒数,这样浏览器在规定时间内,这个接口的请求就不会再进行OPTIONS预检命令,直接执行下一个请求。如果用户在浏览器开发模式中将Disable cache开启,那么就会清掉这个缓存,重新验证OPTIONS预检命令
1 | // OPTIONS请求设置3600毫秒缓存 |
注意,如果使用了反向代理解决跨域问题的时候,那么这些请求实际上都没有跨域,都在同一个域名上请求,不存在简单请求和复杂请求,就没有了OPTIONS这个预检请求了,直接进行请求
带cookie的跨域
ajax会自动带上同源的cookie,不会带上不同源的cookie。前端如果需要发起跨域的带有cookie的请求,需要将XMLHttpRequest.withCredentials
置为true,修改方法为:
1 | $.ajax({ |
这个修改永远不会影响到同源请求,不同域下的XmlHttpRequest
响应,不论其header 设置什么值,都无法为它自身站点设置cookie值,除非它在请求之前将withCredentials
设为true。
以上是前端需要做的改造,后端需要做的改造如下:
1 | res.addHeader('Access-Control-Allow-Origin', '具体网址'); |
注意,这时候后端应用服务器就不能将 Access-Control-Allow-Origin
置为*
这样的通配符形式,一定要用全匹配的地址,而且需要额外配置Access-Control-Allow-Credentials
,通过这样的参数设置,就可以保持跨域ajax时传递cookies。
这时注意,这也不是后台应用服务器将Access-Control-Allow-Origin
写死,由前面得知,浏览器在跨域请求时,会在预检请求OPTIONS里增加一个Origin字段,这时候后台就可以取到这个字段,然后将它赋值给Access-Control-Allow-Origin
,这也就变相得实现通配符的功能~
需要非常注意,我们发送的cookie,是被调用方域名的cookie,cookie要加在被调用方的域名上,而不是调用方的cookie
1 | document.cookie = "name=yuuhei; path=/"; |
带自定义头的跨域
jquery的ajax自定义请求有两种方法:
1 | $.ajax({ |
后端也要做响应处理,不然浏览器会报Access-Control-Allow-Headers
的问题,需要在后台代码加上对应名称的header(像加之前Content-Type的时候那样加),可以先获取由发起方得到的自定义header,再赋值这样的套路(像之前设定Origin时的方法),这样后台代码就不会写死。
express作为后台应用服务器解决方案总结(被调用方解决):
1 | // nginx反向代理解决后这些都不用设置了 |
被调用方- Nginx解决方案
nginx有一个虚拟主机的概念,什么是虚拟主机:
多个域名指向同一个服务器,nginx服务器根据不同的域名,转发到不同的应用服务器
虚拟主机的配置:配置host文件,设定如下类似代码,这样nginx就可以配置多个虚拟域名,其实访问的都是同一个ip地址:
1 | # 127.0.0.1就是本地的ip,b.com就是映射的虚拟主机ip |
配置nginx:打开nginx目录,打开conf目录,新建一个vhost文件夹,然后修改nginx.conf
1 | # nginx.conf 在http块区域里最后面增加 |
在vhost文件夹创建一个conf文件,配置如下:
1 | # vhost文件夹新增.conf |
配置完之后前端请求的地址可以从http://localhost:8080/user.do
变为http://b.com/user.do
,这样就完成了应用服务器的虚拟服务器映射,此时cookies需要加在http://b.com
这个被调用方域名上,像之前所说的
调用方 - 隐藏跨域解决方案,反向代理(目前最好解决方案)
反向代理的意思是,访问统一域名的两个不同的url,会去到两个不同的服务器
nginx目录下,新建vhost文件夹,新增配置a.com.conf,host已做映射
1 | # host配置虚拟地址,模拟线上地址 |
配置nginx.conf
1 | # nginx.conf 在http块区域里最后面增加 |
nginx.conf目录下新建一个vhost文件夹,新建a.com.conf
1 | # a.com |
现在所有的.do请求都会经过3000端口的这个代理
开启nginx(ubuntu下)
1 | nginx -c /etc/nginx/nginx.conf |
就可以通过a.com
和a.com/user.do
就可以同时访问客户端和后台应用服务器,解决跨域问题
这样解决的最大区别是,请求的地址,之前必须是一个绝对地址,现在是一个相对地址。比如之前请求的地址是http://localhost:3000/users.do
,现在只需要请求/users.do
即可
这样的解决方法,之前前后端做的跨域设置都不用做,因为现在经过nginx反向代理后,两个地址都指向同一个域名,无跨域问题,cookie也能够发送到后台应用服务器~