从刚接触前端开发起,“跨域”这个词就一直以很高的频率在身边重复出现,一直到现在,已经调试过N个跨域相关的问题了。
关于跨域,有N种类型,本文只专注于“ajax 请求跨域”( ajax 跨域只是属于浏览器“同源策略”中的一部分,其它的还有 Cookie 跨域 iframe 跨域,LocalStorage 跨域等这里不做介绍)
什么是ajax跨域
ajax 跨域的原理
ajax出现请求跨域错误问题,主要原因就是因为浏览器的“同源策略”。
1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同":协议相同、域名相同、端口相同。
随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,共有三种行为受到限制。
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。
CORS 请求原理
CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。
基本上目前所有的浏览器都实现了 CORS 标准,其实目前几乎所有的浏览器 ajax 请求都是基于 CORS 机制的,只不过可能平时前端开发人员并不关心而已(所以说其实现在CORS解决方案主要是考虑后台该如何实现的问题)。
这里整理了一个实现原理图(简化版):
如何判断是否是简单请求?
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时满足以下两大条件,就属于简单请求。
请求方法是以下三种方法之一:HEAD,GET,POST
HTTP的头信息不超出以下几种字段:
(1)Accept
(2)Accept-Language
(3) Content-Language
(4)Last-Event-ID
(5) Content-Type(只限于三个值 application/x-www-form-urlencoded、 multipart/form-data、text/plain)
凡是不同时满足上面两个条件,就属于非简单请求。
ajax 跨域的表现
ajax 请求时,如果存在跨域现象,并且没有进行解决,会有如下表现:(注意,是 ajax 请求,请不要说为什么 http 请求可以,而 ajax 不行,因为 ajax 是伴随着跨域的,所以仅仅是 http 请求 ok 是不行的)
注意:具体的后端跨域配置请看题纲位置。
第一种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 404
出现这种情况的原因如下:
本次 ajax 请求是“非简单请求”,所以请求前会发送一次预检请求(OPTIONS)
服务器端后台接口没有允许 OPTIONS 请求,导致无法找到对应接口地址
解决方案:后端允许 OPTIONS 请求
第二种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 405
这种现象和第一种有区别,这种情况下,后台方法允许 OPTIONS 请求,但是一些配置文件中(如安全配置),阻止了 OPTIONS 请求,才会导致这个现象。
解决方案:后端关闭对应的安全配置
第三种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且status 200
这种现象和第一种和第二种有区别,这种情况下,服务器端后台允许 OPTIONS 请求,并且接口也允许 OPTIONS 请求,但是头部匹配时出现不匹配现象。比如 origin 头部检查不匹配,比如少了一些头部的支持(如常见的 X-Requested-With 头部),然后服务端就会将 response 返回给前端,前端检测到这个后就触发 XHR.onerror,导致前端控制台报错。
解决方案:后端增加对应的头部支持
第四种现象:heade contains multiple values '*,*'
表现现象是,后台响应的 http 头部信息有两个 Access-Control-Allow-Origin:*
说实话,这种问题出现的主要原因就是进行跨域配置的人不了解原理,导致了重复配置,如:
常见于 .net 后台(一般在 web.config 中配置了一次 origin,然后代码中又手动添加了一次 origin(比如代码手动设置了返回*))
常见于 .net 后台(在 IIS 和项目的 webconfig 中同时设置 Origin:*)
解决方案(一一对应):
建议删除代码中手动添加的*,只用项目配置中的即可
建议删除 IIS 下的配置*,只用项目配置中的即可
如何解决 ajax 跨域
一般 ajax 跨域解决就是通过 JSONP 解决或者 CORS 解决,如以下:(注意,现在已经几乎不会再使用 JSONP 了,所以 JSONP 了解下即可)
JSONP 方式解决跨域问题
JSONP 解决跨域问题是一个比较古老的方案(实际中不推荐使用),这里做简单介绍(实际项目中如果要使用 JSONP,一般会使用 JQ 等对 JSONP 进行了封装的类库来进行 ajax 请求)
实现原理
JSONP 之所以能够用来解决跨域方案,主要是因为 脚本拥有跨域能力,而 JSONP 正是利用这一点来实现。具体原理如图
实现流程
JSONP 的实现步骤大致如下
客户端网页网页通过添加一个元素,向服务器请求 JSON 数据,这种做法不受同源政策限制
请求时,接口地址是作为构建出的脚本标签的 src 的,这样,当脚本标签构建出来时,最终的 src 是接口返回的内容
服务端对应的接口在返回参数外面添加函数包裹层
由于元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了 foo 函数,该函数就会立即调用。作为参数的 JSON 数据被视为 JavaScript 对象,而不是字符串,因此避免了使用 JSON.parse 的步骤。
注意,一般的 JSONP 接口和普通接口返回数据是有区别的,所以接口如果要做 JSONO 兼容,需要进行判断是否有对应 callback 关键字参数,如果有则是 JSONP 请求,返回 JSONP 数据,否则返回普通数据。
使用注意
基于 JSONP 的实现原理,所以 JSONP 只能是“GET”请求,不能进行较为复杂的 POST 和其它请求,所以遇到那种情况,就得参考下面的 CORS 解决跨域了(所以如今它也基本被淘汰了)
CORS 解决跨域问题
CORS 的原理上文中已经介绍了,这里主要介绍的是,实际项目中,后端应该如何配置以解决问题(因为大量项目实践都是由后端进行解决的),这里整理了一些常见的后端解决方案:
PHP 后台配置
PHP 后台得配置几乎是所有后台中最为简单的,遵循如下步骤即可:
第一步:配置 Php 后台允许跨域
第二步:配置 Apache web 服务器跨域(httpd.conf中)
原始代码
改为以下代码
Node.js 后台配置(express 框架)
Node.js 的后台也相对来说比较简单就可以进行配置。只需用 express 如下配置:
JAVA 后台配置
JAVA后台配置只需要遵循如下步骤即可:
第一步:获取依赖 jar 包
下载 cors-filter-1.7.jar, java-property-utils-1.9.jar 这两个库文件放到 lib 目录下。(放到对应项目的 webcontent/WEB-INF/lib/下)
第二步:如果项目用了 Maven 构建的,请添加如下依赖到 pom.xml中:(非 maven 请忽视)
其中版本应该是最新的稳定版本,CORS 过滤器
第三步:添加 COR S配置到项目的 Web.xml 中(App/WEB-INF/web.xml)
CORScom.thetransactioncompany.cors.CORSFilter
cors.allowGenericHttpRequests
true
cors.allowOrigin
*
cors.allowSubdomains
false
cors.supportedMethods
GET, HEAD, POST, OPTIONS
cors.supportedHeaders
Accept, Origin, X-Requested-With, Content-Type, Last-Modified
cors.exposedHeaders
X-Test-1, X-Test-2
cors.supportsCredentials
true
cors.maxAge
3600CORS/*
请注意,以上配置文件请放到 web.xml 的前面,作为第一个 filter 存在(可以有多个 filter 的)
第四步:可能的安全模块配置错误(注意,某些框架中-譬如公司私人框架,有安全模块的,有时候这些安全模块配置会影响跨域配置,这时候可以先尝试关闭它们)
NET 后台配置
.NET 后台配置可以参考如下步骤:
第一步:网站配置
打开控制面板,选择管理工具,选择 iis;右键单击自己的网站,选择浏览;打开网站所在目录,用记事本打开 web.config 文件添加下述配置信息,重启网站
请注意,以上截图较老,如果配置仍然出问题,可以考虑增加更多的 headers 允许,比如:
"Access-Control-Allow-Headers":"X-Requested-With,Content-Type,Accept,Origin"
第二步:其它更多配置,如果第一步进行了后,仍然有跨域问题,可能是:
(1)接口中有限制死一些请求类型(比如写死了 POST 等),这时候请去除限制
(2)接口中,重复配置了 Origin:*,请去除即可
(3)IIS 服务器中,重复配置了 Origin:*,请去除即可
代理请求方式解决接口跨域问题
注意,由于接口代理是有代价的,所以这个仅是开发过程中进行的。
与前面的方法不同,前面 CORS 是后端解决,而这个主要是前端对接口进行代理,也就是:
前端 ajax 请求的是本地接口
本地接口接收到请求后向实际的接口请求数据,然后再将信息返回给前端
一般用 node.js 即可代理
关于如何实现代理,这里就不重点描述了,方法和多,也不难,基本都是基于 node.js 的。搜索关键字 node.js,代理请求即可找到一大票的方案。
如何分析ajax跨域
上述已经介绍了跨域的原理以及如何解决,但实际过程中,发现仍然有很多人对照着类似的文档无法解决跨域问题,主要体现在,前端人员不知道什么时候是跨域问题造成的,什么时候不是,因此这里稍微介绍下如何分析一个请求是否跨域:
抓包请求数据
第一步当然是得知道我们的 ajax 请求发送了什么数据,接收了什么,做到这一步并不难,也不需要 fiddler 等工具,仅基于 Chrome 即可
Chrome 浏览器打开对应发生 ajax 的页面,F12打开 Dev Tools
发送 ajax 请求
右侧面板->NetWork->XHR,然后找到刚才的 ajax 请求,点进去
示例一(正常的 ajax 请求)
上述请求是一个正确的请求,为了方便,小编把每一个头域的意思都表明了,我们可以清晰的看到,接口返回的响应头域中,包括了
所以浏览器接收到响应时,判断的是正确的请求,自然不会报错,成功的拿到了响应数据。
示例二(跨域错误的 ajax 请求)
为了方便,我们仍然拿上面的错误表现示例举例。
这个请求中,接口 Allow 里面没有包括 OPTIONS,所以请求出现了跨域。
这个请求中,Access-Control-Allow-Origin: *出现了两次,导致了跨域配置没有正确配置,出现了错误。
更多跨域错误基本都是类似的,就是以上三样没有满足(Headers,Allow,Origin),这里不再一一赘述。
示例三(与跨域无关的 ajax 请求)
当然,也并不是所有的 ajax 请求错误都与跨域有关,所以请不要混淆,比如以下:
比如这个请求,它的跨域配置没有一点问题,它出错仅仅是因为 request 的 Accept 和 response 的 Content-Type 不匹配而已。
基本上都是这样去分析一个 ajax 请求,通过 Chrome 就可以知道了发送了什么数据,收到了什么数据,然后再一一比对就知道问题何在了。
— END —
免责声明:本文及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理。本声明未涉及的问题参见国家有关法律法规,当本声明与国家法律法规冲突时,以国家法律法规为准。
领取专属 10元无门槛券
私享最新 技术干货