一、什么是跨域问题
前台调用后台服务接口的时候,如果这个接口跟调用方不是同一个域的就会产生跨域问题
二、原因
1. 浏览器的限制
浏览器出于安全的考虑,当它发现你的请求是跨域的时候,就会做一些校验,校验不通过,就会爆出跨域安全问题。
2. 跨域
发出去的请求不是你本域,请求里面协议、域名、端口任何一个不一样,浏览器就认为是跨域。
3. XHR(XMLHttpRequest)请求
如果你发送的不是XHR请求的话,就算是跨域,浏览器也不会报错。
我做两个ajax发送请求,一个正常发送ajax调用接口请求,一个采用图片的形式发送一个普通的请求(直接在image标签中,放入该访问url),结果如上图所示,正常发送的请求,为XHR请求类型,而图片形式发送的普通请求,产生的是json请求。
而此时我们浏览器的错误之报出了一个错误,如下:
所以可以证明,发送的不是XHR请求的话,就算是跨域,浏览器也不会报错。
另外注意:当同时满足上述三个原因,才会发生浏览器跨域安全问题。
三、解决思路和方法
1. 指定参数,让浏览器不去校验
命令行启动chrome,输入: chrome --disable-web-security
价值不大,需要每个人都做客户端的改动
2. 发送的请求不是XHR类型----Jsonp
Jsonp全称为Json for padding,可理解为json的一种补充使用方式,是一个非官方协议,它是一个约定,约定了我请求的参数里面,如果包含指定的参数,默认就是callback,这就是一个jsonp请求。服务器发现这是一个jsonp请求的时候,就会将返回的值由原来的Json对象改为js代码,js代码的内容为函数调用的形式,函数名为callback参数的值,函数参数为原来要返回的json对象。接下来,在服务器端代码,添加如下内容:
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpBodyAdvice(){
public JsonpAdvice(){
super("callback");
}
}
然后我依次在前端js脚本中,用ajax发送了正常的json请求和jsonp请求,结果如下:
从图中可以看出,Jsonp发出去的请求不是XHR类型,返回的响应式是JS脚本,并且请求发出去的时候,加了callback参数,这参数是在服务器端定义的,后台发现有callback参数,就知道这是一个Jsonp请求,于是就会将返回的数据由json变成js,而js的内容就是一个函数调用,也可以简单理解为,Jsonp通过动态创建的script标签,在script标签里面发出跨域请求,是一种变通的解决方案。
另外,要注意的是callback后面可能会跟着“_”参数,参数值是随机的数字,如下:
该参数主要是为了防止请求被缓存,若在ajax请求了设置了cache:true就不会出现这多余的参数值。
jsonp的缺点:
1)服务器代码需要改动
若不改动,浏览器将会将服务器返回的Json对象中的字符串当作JS代码来解析,就会报错。
2)只支持get请求
3)发送的不是XHR请求,这样就用不到该请求的一些特性
3. 解决跨域
典型的JAVAEE框架结构图如下所示:
1)被调用方支持跨域解决思路
被调用方修改代码,让其支持跨域,基于http协议关于跨域方面的要求而做的修改,从a域名调用b域名时,在b域名返回的信息里加些字段,告诉浏览器b允许a调用。浏览器通过校验就不会报跨域安全问题。
常用方法:
1.1 采用过滤器,对返回请求中Header进行头文字的追加允许跨域的信息,根据不同的携带方式请求,进行不同的Header字段设置,使其支持跨域,一般都是增加Access-Control-Allow-Origin(表示允许跨域的域),Access-Control-Allow-Methods(表示允许跨域的方法)两个字段
其中要注意的是带有Cookie的ajax跨域,Access-Control-Allow-Origin的value不能为"*", 其值必须是当前调用方的url,如:res.addHeader("Access-Control-Allow-Origin", "http://localhost:8081");之后把Access-Control-Allow-Credentials的value设置为true.
1.2 在Nginx、apache配置虚拟节点,采用代理模式
其中运用到的就是虚拟主机的概念,多个域名指向同一个服务器,服务器通过不同的域名,将请求转到不同的应用服务器(例如Tomcat),看上去好像多个主机,实际就一个主机。其实本方法,就相当于之前过滤器方法的操作,从应用服务器Tomcat上转到了Nginx中转服务器上。
1.3 spring框架的自带解决方式,其中本人比较推荐spring这种解决方式,只需要在相应的需要支持跨域方法上添加@CrossOrigin注解即可
2)调用方隐藏跨域解决思路
当域名不是自己公司的时,可以用此方法解决。通过一个代理,使得从浏览器发出的请求都是a域名的请求,在代理里面把指定的url转到b域名里面,使得在浏览器上看上去就是同一个域名。
常用方法:
采用Nginx和Apache的反向代理配置,本方法类似于上述被调用方方法中的Nginx和Apache配置,一个是反向代理到其他Nginx上,一个是正向代理到自身不同的应用,并追加Header字段信息进行返回响应。
4. 请求是先执行还是先判断
在解决跨域问题时,尤其在使用被调用方支持跨域的解决思路中,我们需要知道浏览器对请求的执行和判断的顺序。
浏览器在发送跨域请求的时候,会判断请求的类型,判断其是否是简单请求和非简单请求。
简单请求:先执行后判断
非简单请求:先发出一个预检命令,然后在发出请求。先判断后执行。
关于常见的简单请求和非简单请求,如下图所示:
当浏览器发现当前请求时跨域的时候,会在请求头部增加当前域信息的字段,当请求返回来,就会检查响应头里面是否有允许跨域的信息,如果没有,就会报错。