个人博客网:https://wushaopei.github.io/ (你想要这里多有)
AJAX跨域的原因:
产生跨域问题是因为前台需要大量调用后台接口,不在一个区域就产生跨域
解决方法
前后台分离,服务化,前后台独立开发
一、测试环境搭建
-
后台代码编写
-
前台代码编写
-
引入前台jasmine测试框架
1、编写后台服务代码
1)创建springboot项目 ajaxServer.java 来
2)创建测试接口:
3)处理返回值,创建ResultBean.java
4)配置application.yml文件:
5)启动并访问测试接口: localhost:8092/test/get1
2、编写前台页面代码
1)创建springboot项目 ajaxClient.java来
2)编写 前台页面代码
引入jquery.js
3)启动ajaxClient并进行跨域访问:
这里通过在ajaxClient的前端页面进行访问ajaxServer的测试接口。
访问结果:
由控制台可以看到,Console 报了一个错误,这就是跨域访问安全问题
3、引入 jasmine 测试框架
1)下载并装配jasmine到ajaxClient工程中:
下载:
装配:直接解压放到static目录下即可
引入前端测试框架 jasmine,提高开发效率,从官网进入到 Releases目录下下载 jasmine .zip 压缩文件,解压后将lib目录下的包复制到工程项目的static目录下
2)引入jasmine 测试框架后,console 会生成一个界面用于提示用户测试结果及相关错误
三、AJAX跨域的原因分析
1、跨域安全问题:
- 浏览器限制:浏览器对请求进行校验,校验不通过则报跨域安全问题;(此处,前端向后台的请求处理是成功的,后台正常打印数据,前端 console 也正常打印数据,但浏览器在处理时拒绝了展示)
- 跨域:在交互过程中,前台向后端发起请求,前台的ip、端口与后台的ip、端口存在任何一个不同的情况都会被认为是跨域;
- XHR(XMLHttpRequest)请求:发送的请求类型不同,发出的请求Type 为 json 时不会有跨域的问题,而一般情况下,大都是 Type 为 xhr 的请求,所以存在跨域请求的问题
2、浏览器限制:
由截图可知,服务器的请求响应状态为200,这代表这客户端的请求被服务器正常的响应了,服务器后台是没有任何限制,而且正确处理 了;从侧面也就证明了是 浏览器进行了限制,从而报了错误给我们
由图中可以知道,当发送请求时,尽管控制台报了跨域请求错误,但是IDE的服务器后台,依旧正常收到了请求,并打印相关 响应字段
3、跨域:
由图中可知,server 和 client 的 port 端口是不同的,因此,浏览器检测到后认为其发生了跨域
4、XHR(XMLHttpRequest)请求:
测试: 新增加一个 <img>标签,重启启动后,由图可知,尽管新添加了一个 <img>标签,但是,浏览器所报的错误还是只有一个,而这一个便是之前的<a>标签的请求;而这是由于 <img> 和 <a> 两个请求发送时的 Type 不同,<img> 是json类型的,而<a>则是xhr类型的
四、解决思路
跨域请求问题解决方案:
1. 让浏览器不做限制,指定参数,让浏览器不做校验,但该方法不太合理,它需要每个人都去做改动。
2. 不要发出XHR请求,这样就算是跨域,浏览器也不会报错,解决方案是JSONP,通过动态创建一个script,通过script发出请求
3. 在跨域的角度:一种是被调用方修改代码,加上字段,告诉浏览器,支持跨域,支持调用方调用。第二种是调用方使用代理,在a域名里面的的请求地址使用代理指定到b域名。第一种是支持跨域,第二种是隐藏跨域
如图:
五、全面解决跨域问题
1、浏览器禁止检查
1)重启新的浏览器时右键进行 DOS is Here 操作,进入命令行
2)修改浏览器的设置后,没有再出现跨域问题
由此可以认为,跨域问题主要还是在浏览器发生的校验失败等问题导致的,与后台没有任何关系
3)针对该浏览器添加参数,禁止浏览器做校验,避免报跨域问题
在浏览器的安装目录启动终端,输入禁止浏览器做跨域检查的校验参数,新启一个浏览器。命令参数:
chrome --disable-web-security --user-data-dir=g: emp3
2、jsonp解决跨域
1) 修改数据类型为 jsonp 格式
dataType:"jsonp",
使用jsonp 解决跨域问题时,后台代码是需要做改动的,创建一个JsonpAdivice 切面,集成AbstractJsonpResponseBodyAdvice (Jsonp的格式支持接口);
2) 实现接口方法: JsonpAdvice(),
super("callback")这里的意思是调用父类的构造方法对回调的参数进行格式转化(个人理解)
3)问题:
由图一和图二可以知道,当在前端代码将 DataType 改为 jsonp 后,依旧会报跨域问题;因为这时后台传给前端的还是json格式的数据,存在类型的异常问题;所以这里需要对后台代码进行修改,以适应jsonp的数据格式。
解决方案通过创建JsonpAdivice 切面解决
3、jsonp解决跨域2
jsonp解决跨域问题的原理(区别):
①普通请求,发送请求时,请求类型默认为 xhr ;jsonp的请求类型为 script ,不会被浏览器认为是跨域异常;
②普通请求返回的数据类型默认为 json 格式;而 jsonp 的请求返回的数据类型为 javascript 的脚本;
③普通请求的地址后面没有携带任何数据;而jsonp请求的地址后面携带了 callback为键的一组键值对数据;
由下列图片可知,
当前端被设置为jsonp格式后,在后端中使用切面实现 jsonp 的格式化接口;该接口声明当返回的数据有callback这个值时,将返回的数据以jsonp的格式返回给前端,避免出现数据类型不一致的问题。
JSONP的原理:
jsonp是一种非正式传输协议,是前后台约定的协议,而不是官方协议。
jsonp的实现原理是:
前后台约定带有“callback”这个参数的请求就是jsonp请求,前台发出去的请求加了“callback”参数,当后台发现请求中带“callback”时,后台就知道这是一个jsonp请求,就会把返回的数据由json变成JS代码返回,JS代码内容就是一个函数的调用,函数名是“callback”参数的值,而原来需要返回的json对象数据在这里作为参数传递返回。
4、json解决跨域:
JSONP是一个非官方协议,它是一个约定,约定了一个请求的参数里如果包含指定的参数(默认是callback)这就是一个JSONP请求,服务发现是一个JSONP请求时就会把返回的值由原来的JSON对象改变JS代码,JS的代码的内容是函数调用的形式。他的函数名是callback参数的值,它的函数的参数是原来 要返回的JSON对象。
JSONP的弊端:
①前后的代码都需要做支持上的改动;
②不支持 PUT 和 DELETE 请求;
③发送的不是XHR请求,是script
5、最常见的javaee架构
从服务端解决跨域问题:
(1)被调用方解决跨域问题,被调用方修改代码解决。
(2)调用方解决跨域,调用方修改Apache或者Nginx静态服务器,通过静态服务器隐藏调用请求返回给前台,前台以为是同一个地址和端口的请求就解决了跨域问题。
一个请求是怎么被处理的:当一个请求从浏览器发出的时候,他会先到中间的http服务器或者叫静态服务器主要是由(apache/nginx)来处理的。中间的静态服务器收到请求后会判断是静态请求还是动态请求,和用户数据有关的就是动态请求,如图片,css,js就是静态请求,中间的静态服务器发现是静态请求时就会把请求直接处理,然后直接返回到客户端,如果是动态请求,请求从客户端发起到了中间的http服务器,中间的http服务器会把请求转发到后台的应用服务器处理完后再返回给中间的http服务器,http服务器再把请求转发到客户请求。
6、跨域解决方向
被调用方解决:是基于支持跨域的解决思路,基于http协议关于跨域方面的一些规定,在响应头里加允许调用字段,跨域请求是直接从浏览器发送过去的;
调用方解决:是基于隐藏跨域的解决思路,跨域请求不会直接从浏览器发到被调用方,而是从中间的http服务器转发过去的。
举例子:
调用方为a.com,被调用方为b.com。
被调用方解决时,在浏览器上会看到b.com的url,修改的是被调用方的http服务器。
调用方解决时,在浏览器上看到的都是a.com的url,但是该到b.com的请求还是会到b.com的。修改的是调用方的http服务器。
7、被调用方解决跨域
-
服务器端实现
-
NGINX配置
-
Apache配置
8、被调用方—Filter解决方案:
1) 浏览器是先执行后判断
2)跨域请求和普通请求的区别:
跨域请求的请求头中多了一条信息:如下
origin : http://localhost:8081
这个值是当前请求的域名信息,如果 浏览器发现当前请求是跨域的时候,它就会在当前请求头中添加一个当前域的信息的字段;然后在请求返回的时候,它会检查响应头里面有没有允许跨域的信息存在,如果没有,它就会报错。
跨域请求:
在被调用方角度: 使用Filter 解决方案
filter解决跨域方案:
①创建一个Bean,在bean中创建FilterRegistBean 的实例,根据该实例声明过滤的对象.addUrlPatter("/*");
②声明过滤器实例, .setFilter(new CrosFilter());
③在CrosFilter的方法中声明允许跨域请求的请求地址和请求方法,如果要通过所有的请求和地址,只需要将指定的地址和方法名修改为 "*" 即可
结果:
经过创建的 过滤器的过滤操作后,filter 解决 了 浏览器对跨域请求的拦截,当有多个地址需要进行相同操作时,可以使用"*"代替具体的地址,从而实现大范围的跨域请求
9、简单请求和非简单请求
1)简单请求是先请求,浏览器再判断是否是跨域;而非简单请求要先发送一个预检命令,检查通过之后才会真正的把跨域请求发出去
工作中比较常见的【简单请求】:
方法为:
GET
HEAD
POST
请求header里面:
无自定义头
Content-Type为一下几种:
text/plain
multipart/form-data
application/x - www - form - urlencoded
2)非简单请求常见的是发送json格式的请求,如图contentType为
最后的结果如下,返回了两个请求,一个是OPTIONS,另一个是POST请求,其中的OPTIONS就是一个预检命令,成功了之后才会发送后面的跨域请求
3)预检命令的缓存。因为这种非简单请求每次都会发送两次请求,其实效率是比较低的,但是如果能缓存预检命令的话,会响应的提高效率
在服务器中,添加一个响应头信息,告诉浏览器在截下来的一个小时可以缓存信息,就不需要再发送预检命令
问题1:浏览器是先执行请求还是先判断跨域?
浏览器请求-->判断响应中是否有允许跨域-->发现不允许跨域,阻止跨域
说明:当执行跨域请求时,浏览器会提示当前接口不被允许,这说明浏览器已发出了当前请求,但是它的的响应内容被拦截;如果在Response header中的Access-Control-Allow-Origin设置的允许访问源不包含当前源,则拒绝数据返回给当前源。
当浏览器要发送跨域请求时,如果请求是复杂请求,浏览器会先发送一个options预检命令即一个options请求,当该请求通过时才会再发送真正的请求。
该option请求会根据请求的信息去询问服务端支不支持该请求。比如发送的数据是json类型(通过content-type设置)的话,会携带一个请求头Access-Control-Request-Headers: content-type去询问支不支持该数据类型,如果支持,则请求就会通过,并发送真正的请求
10、缓存预检
1)预检命令的缓存
1、res.addHeader("Access-Control-Allow-Headers","Content-Type");
2、res.addHeader("Access-Control-Max-Age", "3600"); 3600秒,告诉浏览器1个小时之内不要清除这段缓存信息。
2)我们知道非简单请求, 每次会发出两次请求, 这会影响性能. HTTP协议增加了个响应头, 可以让我们在服务端设置`Access-Control-Max-Age`来缓存预检请求, 比如说我们可以设置为3600m, 也就是一小时客户端只会在第一次的时候发送两个请求, 接下来一个小时内`OPTIONS`请求就被缓存起来了.
11、带cookie的跨域
当产生跨域的时候,请求头中会多一个字段,叫做origin,这个字段有当前域的信息。所以在发送带cookie的请求,后台又不知道调用方的域的信息时,可以先取到请求头中origin字段的值再赋值给响应头的access-control-allow-origin字段中。
11.1、http会话session依赖于cookie, sessionid存放在cookie中。
11.2、ajax
1) $.ajax({
type: "get",
xhrFields: {
widthCredentials: true // 发送ajax请求的时候会带上cookie
}
})
2)cookie是加在被调用方。
服务端就是被调用方,而客户端就是调用方。在浏览器的控制台中通过document.cookie="" 来设置cookie。
3)读cookie只能读到本域的。
4)带cookie的跨域时,后台代码注意以下2点:
① 带cookie的时候,Access-Control-Allow-Origin,必须是全匹配,如http://localhost:8081, 不能是 *,否则报错,如下:
② 带cookie进行跨域时,需要设置以下请求头:
res.addHeader("Access-Control-Allow-Credentials", "true")
发送的cookie是被调用方域名的cookie
不写*,指定了某个域名,其他域名要跨域访问的操作
后端过滤器(CrossFilter)
12、自定义请求头的跨域
12.1 自定义请求头
12.2 自定义跨域后台请求
12.3 自定义请求头在浏览器 Headers中
12.4 在过滤器中声明自定义的请求头,以解决自定义请求跨域的问题
请求头参数:"Content-Type,x-header1,x-header2"
12.5 使用自动声明获取自定义请求头的参数的方式,再将参数添加到过滤器中 req.getHeader(“自定义请求头”);
github:https://github.com/wushaopei/SPRING_BOOT/tree/master/Spring-boot-ajaxs