- 这是一篇本应很久之前就该发布的博客,奈何我健忘(懒)
00 | 可恶!报错了!
- 某天,我在写代码,但是报错了,如下图:
于是我进行debug,在出现异常的地方,我发现返回的 response 是 undefined 的,并且 message 消息中只有一个"Network Error"。 于是一番百度,发现是跨域问题。 - 有个问题,浏览器是发不出请求,还是能发送请求,但是回来的请求被拦截了呢?
可以用charles抓个包,发现charles不仅抓到了一个请求,甚至连返回内容都有了,所以对于普通的网络请求,浏览器会先发请求,待收到请求的返回之后,校验请求的response headers,再根据同源策略和response headers的内容判断能不能允许请求正常返回给js代码。
01 | 什么是跨域问题
- 前端调用的后端接口不属于同一个域(域名或端口不同),就会产生跨域问题,也就是说你的应用访问了该应用域名或端口之外的域名或端口。
- 为什么会出现
response: undefined
?
请求已经发出去了,服务端也接收到并处理了,但是返回的响应结果不是浏览器想要的结果,所以浏览器将响应结果给拦截了,所以会看到response是undefined。
02 | 为什么会发生跨域问题
那么问题来了,为什么浏览器端会将返回的结果给拦截掉?
- 因为有浏览器的同源策略哦宝,服务器之间没有跨域的说法,所以跨域只存在于浏览器上
浏览器的同源策略是什么
- 所谓“同源”就是指"协议+域名+端口"三者相同,如果没有同源策略的保护,就会产生CSRF攻击(跨站请求伪造),CSRF攻击就是说,比如我去 alinxi.top 上买了件衣服,并且输入了支付账号密码进行支付,浏览器在本地缓存了我银行卡的账号密码的cookid,但是我不小心,点了个钓鱼网站,他获取我的cookie,伪装成是我进行转账。
同源策略会限制什么行为呢
- Cookie 、LocalStorage 和 IndexDB无法读取
- 无法获取或操作另一个资源的DOM
- AJAX请求不能发送
03 | 跨域问题解决思路
哎呀,什么是CORS呢?
- CORS是"跨域资源共享"(Cross Origin Resource Sharing),它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了只能发送同源请求的限制。
怎么解决跨域问题呢?
-
法一:甩锅给前端,让他用JSONP啦
原理就是利用浏览器对于script、img标签在跨域上的“特殊处理”,它们并没有跨域限制。因此,当我们需要用到跨域请求时,可以在浏览器中构建一个标签,设置他的src属性为要请求的地址,让这个标签去请求服务器。but,JSONP只能处理GET请求,如果想处理POST请求的话,可以用iframe+form来解决,用iframe发送数据,使用form执行表单提交。前端用jsonp,相应的后端也要进行变动,springboot1.5版本里,添加一个切面来支持JSONP请求,springboot2.多版本中废弃了一个类。 -
法二:用个代理服务器吧
比如说Nginx和webpack-dev-server,webpack-dev-serve的原理主要也是服务器之间的请求是没有跨域的,它会用express起一个服务,然后将所有请求传到指定的URI。这个时候我们可以使用Nginx来做转发,进行server{}配置一下就好了。
代理服务器的曲线救国方式就是说,先让ajax请求同源地址,然后让同源的服务器再请求目标地址,得到目标地址响应后,再将响应转交给客户端。因为代理服务端用的是服务器技术比如php什么的,去请求目标服务器,不是用js,所以不会有同源策略的限制。因为发了两次请求,所以效率上会差一点。 -
法三:那就后端辛苦一下吧:CORS
通过增加一系列请求头和响应头来实现跨域,比如Access-Control-Request-Headers
,Access-Control-Allow-Origin
是该字段表示,服务端接收哪些来源的域的请求。后端解决方式种类如下:-
在 Java Web 中,可以添加一个拦截器来设置相应的参数,而如果用springboot的话直接在 Controller 类上加上 @CrossOrigin注解就可以了。
-
或者通过 CorsRegistry 设置全局跨域配置,如果你使用的是 Spring Boot,推荐的做法是只定义一个
WebMvcConfigurer
的Bean。对于第二种方式在没有定义 拦截器(Interceptor) 的时候,使用一切正常,但是如果你有一个全局的拦截器用来检测用户的登录态,当自定义拦截器返回true时,一切正常,但是当拦截器抛出异常(或者返回false)时,后续的CORS设置将不会生效。主要是因为,
AbstractHandlerMapping
在添加CorsInterceptor
的时候,是将Cors
的拦截器 加在拦截器链的最后,那就会造成上面说的问题,在自定义拦截器中抛出异常之后,CorsInterceptor
拦截器就没有机会执行向response
中设置 CORS 相关响应头了。相应的解决方案,就是将用来处理Cors
的拦截器CorsInterceptor
加在拦截器链的第一个位置就ok了。这是一个国外的哥提的issue,感觉是一个可行的解决方案,但是 Spring Boot 的成员认为这不是 Spring Boot 的Bug,而是 Spring Framework 的 Bug,所以将这个issue关闭了。 -
通过CorsFilter 过滤器设置全局跨域配置,过滤器可以而拦截器不行的主要原因是:因为过滤器依赖于 Servlet 容器,基于函数回调,它可以对几乎所有请求进行过滤。而拦截器是依赖于 Web 框架(如Spring MVC框架),基于反射通过AOP的方式实现的。因为过滤器在触发上是先于拦截器的,但是如果有多个过滤器的话,也需要将 CorsFilter 设置为第一个过滤器才行
-
04 | 参考资料
- https://zhuanlan.zhihu.com/p/31463931
- https://zhuanlan.zhihu.com/p/66484450
- https://zhuanlan.zhihu.com/p/145837536
- MDN相关资料
完成。