跨域问题——知其然知其所以然
最近做项目前后端分离才第一次遇到跨域问题,作为渣渣,当然是虚心学习网络上一些前辈的经验,以下是自己的总结。哪个地方有错,还望告知,定虚心请教。
文章内容:
- 跨域原因
- 解决思路
- 解决方法
一、跨域原因
现在的web项目基本是前后端分离,前端调用后端接口,如果前后端代码不属于同一个域(同一域名同一端口号),就会产生跨域问题。
常见的跨域问题的报错提示是:
Failed to load “被调用方域名”: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘调用方域名’ is therefore not allowed access.
那么,为什么会发生AJAX跨域问题?
- 浏览器限制
- 请求跨域访问不同域
- XHR(XMLHttpRequest)请求
当上述三个条件同时满足时,就触发了跨域问题啦~
二、解决思路
三、解决方法
1.去掉浏览器限制(以下用chrome做演示)
这种方法并不具有可行性,你没办法让浏览器都这样或者每个用户自己实现。
启动chrome时加 --disable-web-security
禁止它做跨域校验。
2.使用JSONP解决
首先来了解一个JSOP是什么?
JSONP是一个非官方协议,约定发送请求的参数中如果包含指定的参数,默认为callback.即JSONP请求,服务器发现其指定参数时,就会把返回值由原来的JSON对象改成JS代码。
JSONP通过动态创建JS在JS中发出请求,用完就销毁,js代码的内容是函数调用的形式,它的函数名是callback的值,它的函数的参数是原先json对象。
其JS函数后面带着时间戳防止被浏览器缓存。此时Content-Type=application/javascript
使用JSONP时服务器端代码需要进行改动(这里用Java代码示例)
JSONP的弊端:
- 需要改动服务器端
- 只支持GET(因为是动态创建script,script只能GET方法)
- 发的不是XHR请求(现在XHR有各种特性用于前端开发)
下面,我们来谈谈跨域问题主流的两种解决方式吧
3.被调用方解决
- Filter解决方案
- nginx解决方案
- apache解决方案
- Spring框架解决方案
1.Filter解决方案
采用这种解决方案之前先来看看下面几个问题。
浏览器对于请求是先执行还是先判断是否跨域?
先执行。
1.是不是所有请求都是先执行呢?
回答这个问题需要弄明白简单请求和非简单请求
浏览器对于简单请求,就是先执行后判断。对于非简单请求,它会先发一个OPTIONS预检命令,检查通过再执行。
2.那么简单请求和非简单请求有哪些呢?
常见的【简单请求】:GET、HEAD、POST
请求header里面
无自定义头
Content-Type为以下几种:
text/plain
multipart/form-data
application/x-www-form-urlencoded
常见的【非简单请求】:
PUT、DELETE的ajax请求
发送json格式的ajax请求
带自定义头的ajax请求
3.浏览器如何判断?
浏览器发现请求是跨域的时候,它会在当前请求的请求头中增加origins字段,然后等请求响应回来,
浏览器会检查响应头中是否存在允许跨域的信息,没有就报错。
4.带Cookie的跨域——Access-Control-Allow-Origin: *是否满足所有跨域场景?
请看下图:
答案显然是否,对于带Cookie的跨域请求必须明确指定Access-Control-Allow-Origin的域名,
并设置Access-Control-Allow-Credentials为true。
最后,来看下Filter解决方案的示例代码吧。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
HttpServletRequest req = (HttpServletRequest) request;
String origin = req.getHeader("Origin");
if (!org.springframework.util.StringUtils.isEmpty(origin)) {
//带cookie的时候,origin必须是全匹配,不能使用*
res.addHeader("Access-Control-Allow-Origin", origin);
}
// 允许所有请求方法
res.addHeader("Access-Control-Allow-Methods", "*");
String headers = req.getHeader("Access-Control-Request-Headers");
// 支持所有自定义头
if (!org.springframework.util.StringUtils.isEmpty(headers)) {
res.addHeader("Access-Control-Allow-Headers", headers);
}
res.addHeader("Access-Control-Max-Age", "3600");
// enable cookie
res.addHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(request, response);
}
2.nginx解决方案
3.apache解决方案
4.Spring框架解决方案
controller类上加@CrossOrigin注解
4.调用方解决——隐藏跨域
调用方解决跨域问题只有一种方式,就是利用Http服务器进行隐藏跨域啦,下面给出nginx和apache两个服务器隐藏跨域的配置方式
1.反向代理——nginx配置
调用方设置nginx反向代理
2.反向代理——Apache配置
调用方设置Apache反向代理
推荐阅读