若想自己实践ajax跨域访问的例子,要编写后台服务代码及前台页面代码。网上很多文章都有贴代码都说过怎么解决这个问题,这篇文章主要是分析产生的原因和解决的思路。
前台调用后台服务接口的时候,如果这个接口不是同一个域的就会产生跨域问题。现在的开发模式是前后台分离发展,前后台开发是独立,所以就会出现前台大量调用后台接口的场景,只要不是同一个域就产生跨域问题。
比如我开发的是http://localhost:8080
ajax调用公司的接口http://xxx.com/xxx/xx/x
这就会报跨域错
三个原因,三个同时满足才产品跨域问题
1.浏览器限制
2.跨域
3.是XHR请求
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js">script>
在network里查看
发现跨域也没关系。
JQuery的ajax方法对JSONP方法进行了封装,使用起来很方便。
这三个同时满足才有可能发生跨域安全问题
命令行参数启动:
DOS打开浏览器并加一些参数,参数作用就是禁止做校验。这个新开的浏览器不会报错,所以跨域问题是前台的校验。
用户是不会愿意这样去打开浏览器的,多麻烦
json的一种补充使用方式,不是官方协议,但也是个协议约定,利用‘script标签请求资源可以跨域’来解决跨域的。
先对前端代码改动,主要是dataType那里。
发现报语法错误,jsonp是动态创建一个script标签,script标签返回的应该是js代码(比如引jquery库返回js代码),但现在服务器后台没有做任何改动,返回的还是一个json对象,所以浏览器把json对象和字符串当作js代码来解析,所报错。所以使用jsonp后台是需要改动的
查看network
可以看出普通的ajax返回的是json,jsonp返回的是js
再看jsonp请求后面自动加了callback=一长串,双击点开如下
发现刚才callback=的一长串就是返回的js代码的函数名。而数据,这个json对象是作为参数返回的。
所以可以看出jsonp的实现原理是:
jsonp是个协议约定。jsonp请求发出时自动加一个callback参数(jq库自己实现),后台发现有callback就知道这是jsonp请求,它就会把返回的数据由json变为js(后台处理),而js的内容就是一个函数调用。函数名为callback值,返回的数据作为返回的参数。
【加深印象】
为了更好理解我们来验证一下
我打开慕课网,再打开network,发现里面只有一个xhr请求
双击点开
发现里面是个json对象,在地址后面手动加一个callback参数,如下
发现此时返回的是一个以wangyue为函数名,json为返回参数的函数
说明其后台有对jsonp的支持。
在淘宝上找到了jsonp的应用
【思路】:后台在响应头Response Headers增加字段,告诉浏览器我允许跨域调用。
浏览器是先执行后判断,那么浏览器如何判断是跨域请求?
跨域请求的请求头里多了一个origin字段。这个字段的值是当前的域名信息。
就是说,浏览器发现这个请求是跨域的时候就会加在请求头Request Headers里加一个origin字段,这个字段里的值是当前域名。等请求返回来,它就会检查响应头Response Headers里面有没有允许跨域的信息,如果没有就会报错。
所以在后台进行修改
使其返回一个带有Access-Control-Allow-Methods(允许的方法)和Access-Control-Allow-Origin(允许的域)字段的Response Headers. *是代表所有
之前说浏览器是先执行后判断。
浏览器在发送跨域请求的时候会判断一下是简单请求还是非简单请求,简单请求:先执行后判断。如果是非简单请求,会先发一个预检命令,检查通过之后才会真正把跨域请求发出去。
详见链接:举例说明简单请求与非简单请求
Access-Allow-Control-Origin:*是否满足所有场景
错。不能满足带cookie的跨域请求。
带cookie时候Access-Allow-Control-Origin只能全匹配,不能使用星号,还要加一个access-control-allow-credentials: true这个字段。
比如csdn中的一个请求如下:
那如果想支持别的域呢?
(后台处理)可以在请求头里得到orgin字段,把它给响应头的Access-Allow-Control-Origin,那这样就可以支持所有域的跨域调用了。
(后台处理)在请求头里得到access-control-request-headers字段信息,把它给响应头的access-control-allow-headers,那这样就可以支持所有自定义头
在实际应用里跨域请求都是从调用方的浏览器发送到被调用方的http服务器,http服务器再把请求转到应用服务器,应用服务器处理之后返回到http服务器,http服务器再把响应转回给调用方浏览器。如下图所示
那就在两个地方可以增加响应头字段,一个时应用服务器用filter增加,另一个是被调用方HTTP服务器增加。前端:需要把调用的域名改一改,需要把请求发送到被调用方的http服务器而不是应用服务器。(之前在应用服务器用filter处理)
虚拟主机:多个域名指向一个服务器,服务器根据不同的域名将请求转到不同的应用服务器。看上去好像有一个主机,其实是多个主机。
以nginx为例,apache思路也是如此只是写法不同
之前说的解决方案都和具体的框架没有关系。好比filter是每个web应用都有的。如果使用的是spring框架那么解决跨域非常简单。直接加个注解就可以了。
@CrossOrigin
这个注解可以加在类上也可以加在方法上,加在类上就是该类内的所有方法都支持跨域
为支持跨域服务端增加的五个响应头:
access-control-allow-methods:*//支持所有方法
access-allow-control-origin:从origin获取//支持带cookie的所有域
access-control-allow-credentials: true//支持cookie
access-control-allow-headers:从access-control-request-headers获取//支持自定义头
access-control-max-age:3600//一个小时之内的预检命令缓存
可以直接在应用服务器用filter处理增加字段/但大多数实际应用里是在http服务器增加
当无法修改被调用方的时候,就可以采取隐藏跨域的解决思路。
跨域请求经过调用方的http服务器的反向代理转发到被调用方的服务器,在浏览器上面看不到任何的跨域请求。
【反向代理】:你访问同一个域名的两个不同url,它最后会去到两个不同服务器
要对http服务器nginx/apache进行配置
注意:此处8080是后台地址,8081是前台地址
再改下前台代码
把这个改为代理之后的地址
启动nginx,这时客户端不能访问8081端口,让所有的请求经过http服务器,所以要访问a.com
隐藏跨域和支持跨域最大的不同是我们调用的url,隐藏跨域的解决方案上我们调用的url都是本域的所以都是相对地址,而之前支持跨域的解决思路里,这里必须是绝对地址。浏览器看到都是相对地址,都是同一个域的地址,所以不会有任何跨域问题。
给它增加一个虚拟主机,再虚拟主机里把跨域请求做一个代理。打开apache的配置文件,找到虚拟主机的配置文件,增加一个节点
再改下前端代码里的url
打开a.com,没有报错,查看network里的请求地址