1.简介
通过本文了解是跨域问题,从系统最常见的部署结构上分析跨域解决的思路,详细讲解jsonp的工作机制,http协议如何支持跨域,以及http服务器nginx和apache的2种不同解决思路,让大家知其然并知其所以然,快速掌握问题本质和分析问题的方法。
2.什么是跨域
JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。那什么是跨域呢,简单地理解就是因为JavaScript同源策略的限制,a.com域名下的js无法操作b.com或是c.a.com域名下的对象。
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
有一点必须要注意:跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。
2.跨域产生的原因
- 浏览器限制。如果浏览器发现请求是跨域的时候,就会做校验,如果校验不通过就会报跨域的错误
- 发送的XHR(XMLHTTPRequest)请求。如果发送的不是xhr请求,无论是否跨域,浏览器都不会报错
只有这三种原因同时满足时,才会发生跨域
3.解决跨域的思路
浏览器角度。
让浏览器不做校验,浏览器的设置跨域,在浏览器的属性设置页面的目标输入框加上–disable-web-security。这样浏览器将不会去做校验了。但是每个人都需要去改动,==不建议使用。==
发送xhr请求角度。
让跨域的请求不发送xhr请求,就不会再报错了。办法是使用jsonp。
jsonp是非官方协议,是前后端的一种约定,前端使用ajax发送请求,dataType为jsonp,并且带一个参数(默认是callback),当后台发现这个参数之后,就知道这是一个jsonp请求,就会把原本返回json对象编程js返回,js代码是一个函数的执行,函数名是callback的参数值,函数的参数是原本的json对象。
jsonp缺点:
- 1).只支持get方法请求;
- 2).==需要服务器修改代码;==JSONP需要后台配合, 要有一个参数, 默认是?callback=JQuery1234567… JSONP返回的类型是:javascript, 而非json
- 3).发送的不是XHR请求(这也是它可以跨域的原因),没有回调及事件的特性
在跨域角度。
- 1).被调用方解决:在响应头增加指定字段,告诉浏览器,允许调用,这种方法的原理是从根源支持跨域的。
- 2).调用方解决:这种解决办法原理是隐藏跨域。使用代理,在同一个域请求不同的url地址,转发到不同的域。
4.具体解决跨域问题的方法
4.1 跨越问题的现象
通过浏览器访问A项目,项目中打开的页面中将通过JavaScript代码访问B项目的地址。出现跨域问题。
A项目Javascript代码(调用方代码)
1 | <head> |
正常访问,服务端返回响应码为200,但是页面会报错。
浏览器的Console中会打印报错信息:
4.2 基于Jsonp 解决
4.2.1 前端代码改动
在发送ajax请求的时候,设置dataType为jsonp,即告诉后台我需要返回的是script代码,而不是一个json格式的数据。同时==jsonp:“callback”(默认)==,但是可以修改为jsonp:“callback2”这个就是前后台的协议,即后台返回的script的方法的名称,然后后台将原本的json放在该方法的参数中返回给前端,故服务端代码应该也以相同名称返回。
1 | // 测试方法 |
此时会发生两种变化:
- 1请求的地址发生改变
- 2发起请求的类型不在为xhr,而是script。返回的数据类型也为 application/javascript
4.2.2 服务端代码改动
服务端需要新增@ControllerAdvice 修饰的方法。默认请求的传递参数为 callback。如果客户端javascript代码中以jsonp:“callback2”访问服务端,服务端也应该以callback2 相同的字符串返回。
1 | import org.springframework.web.bind.annotation.ControllerAdvice; |
返回内容为javascript内容:
4.2.3 核心原理
引入jquery非压缩的js库,可以在浏览器查看JQuery实现jsonp的时候是动态创建异步javascript的代码。
在浏览器查看jsonp请求里除了callback参数外,还有一个名为下划线“_”的参数,值为一串随机数。此参数作用是防止请求被缓存。如果你的请求可以被缓存的话,可以在请求里使用cache:true。
4.3 常见的JAVAEE 架构中的解决跨域问题的方案
4.3.1 跨域解决方向
被调用方解决
调用方解决
4.3.2 被调用方解决-支持跨域
根据http协议关于跨域方面的要求,增加响应头信息,告诉浏览器允许被跨域调用
4.3.2.1 使用filter解决
res.addHeader(“Access-Control-Allow-Origin”, “http://localhost:8081");
res.addHeader(“Access-Control-Allow-Methods”, “GET”);//设置允许GET方法访问,*表示所有方法
4.3.2.1.2 简单请求与与非简单请求
简单请求:先执行后判断
非简单请求:当浏览器要发送跨域请求时,如果请求是复杂请求,浏览器会先发送一个options预检命令即一个options请求,当该请求通过时才会再发送真正的请求。
两种请求的区分:
如下为非简单请求
该option请求会根据请求的信息去询问服务端支不支持该请求。比如发送的数据是json类型(通过content-type设置)的话,会携带一个请求头Access-Control-Request-Headers: content-type去询问支不支持该数据类型,如果支持,则请求就会通过,并发送真正的请求。
4.3.2.1.3 非简单请求跨域基于Filter解决办法
1 |
|
自定义Filter(同时解决带cookie的跨域、以及自定义请求头)
1 | import javax.servlet.Filter; |
4.3.2.2 nginx解决方案
在被调用方的代理服务器nginx 上增加请求头信息
4.3.2.3 APACHE解决方案、
在被调用方的代理服务器apache上增加请求头信息
4.3.2.4 Spring解决方案
在Controller层增加@CrossOrigin注解即可
4.3.3. 调用方使用代理做调用解决跨域问题-隐藏跨域
4.3.3.1 基于nginx使用反向代理
反向代理:访问同一个域名的两个url,会去到两个不同的服务器. javascript代码中的ajax请求地址也应该为相对地址
4.3.3.2 基于apache使用反向代理
5.参考
慕课网 https://www.imooc.com/learn/947
6.欢迎关注米宝窝,持续更新中,谢谢!
[米宝窝 https://rocklei123.github.io/ ](https://rocklei123.github.io/)