ajax跨域完全讲解

个人博客网:https://wushaopei.github.io/    (你想要这里多有)

AJAX跨域的原因:

产生跨域问题是因为前台需要大量调用后台接口,不在一个区域就产生跨域

解决方法 

前后台分离,服务化,前后台独立开发

一、测试环境搭建

  • 后台代码编写

  • 前台代码编写

  • 引入前台jasmine测试框架

1、编写后台服务代码

1)创建springboot项目 ajaxServer.java 来

2)创建测试接口:

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/get1")
    private ResultBean get1(){
        System.out.println("TestController.get1()");
        return new ResultBean("get1 OK");
    }
}

3)处理返回值,创建ResultBean.java

public class ResultBean {

    private String data;

    public ResultBean(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

4)配置application.yml文件:

server:
  port: 8092

5)启动并访问测试接口: localhost:8092/test/get1

2、编写前台页面代码

1)创建springboot项目 ajaxClient.java

2)编写 前台页面代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="jquery-1.11.3.js"></script>
    <title>Title</title>

</head>
<body>
    
    <!--<img src="http://localhost:8092/test/get1">-->

    <a href="#" onclick="get1()">发生get1()请求</a>
    <script>
        function  get1() {
            $.getJSON("http://localhost:8092/test/get1").then(
                function (result){
                    console.log(result);
                });
        }
        </script>
</body>
</html>

引入jquery.js

3)启动ajaxClient并进行跨域访问:

这里通过在ajaxClient的前端页面进行访问ajaxServer的测试接口。

访问结果:

由控制台可以看到,Console 报了一个错误,这就是跨域访问安全问题

3、引入 jasmine 测试框架

1)下载并装配jasmine到ajaxClient工程中:

下载:

装配:直接解压放到static目录下即可

引入前端测试框架 jasmine,提高开发效率,从官网进入到 Releases目录下下载 jasmine .zip 压缩文件,解压后将lib目录下的包复制到工程项目的static目录下

2)引入jasmine 测试框架后,console 会生成一个界面用于提示用户测试结果及相关错误

三、AJAX跨域的原因分析

1、跨域安全问题:

  1. 浏览器限制:浏览器对请求进行校验,校验不通过则报跨域安全问题;(此处,前端向后台的请求处理是成功的,后台正常打印数据,前端 console 也正常打印数据,但浏览器在处理时拒绝了展示)
  2. 跨域:在交互过程中,前台向后端发起请求,前台的ip、端口与后台的ip、端口存在任何一个不同的情况都会被认为是跨域;
  3. 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是加在被调用方。

https://img1.mukewang.com/5d6bd4a50001420610810534.jpg

服务端就是被调用方,而客户端就是调用方。在浏览器的控制台中通过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)

if(!org.springframework.util.StringUtils.isEmpty(origin)){
    res.addHeader("Access-Control-Allow-Origin",origin);
    }

12、自定义请求头的跨域

12.1 自定义请求头

12.2 自定义跨域后台请求

	@GetMapping("/getHeader")
	public ResultBean getHeader(@RequestHeader("x-header1") String header1,
			@RequestHeader("x-header2") String header2) {
		System.out.println("TestController.getHeader()");
		
		return new ResultBean("getHeader " + header1 + " " + header2);
	}

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

原文地址:https://www.cnblogs.com/wushaopei/p/11979130.html