浏览器有一个重要的安全策略,叫做“同源策略”。其中,source=protocol+host+port,**两个源相同,称为同源,两个源不同,称为跨源或跨域同源策略是指如果页面源与页面运行时加载的源不一致,出于安全考虑,浏览器会对跨域资源访问进行一些限制同源策略是ajax的跨域限制中最严厉的限制。默认不允许ajax访问跨域资源。所以,我们通常所说的跨域问题就是同源策略对ajax的多重影响。解决跨域问题的方法,常见的有:proxy,常用的CORS,常用的JSONP不管用哪种方法,都要让浏览器知道我这次的跨域请求是我自己的,所以不要拦截它。跨域解决方案1-proxy对于前端开发来说,大部分的跨域问题都是通过proxy来解决的。proxy的适用场景是:生产环境不跨域,开发环境跨域。因此,只需要在开发环境中使用代理就可以解决跨域问题。这种代理也称为开发代理。实际开发中,只需要对开发服务器做一点配置就可以完成//vue开发服务器代理配置//vue.config.jsmodule.exports={devServer:{//配置开发服务器代理:{//配置代理"/api":{//如果请求路径以/api开头target:"http://dev.taobao.com",//转发到http://dev.taobao.com},},},};跨域解决方案2-JSONP在CORS出现之前,人们想到了一个绝妙的跨域实现方式,就是JSONP。要实现JSONP,需要浏览器和服务器之间的无缝协作。JSONP的方法是:当需要跨域请求时,不使用AJAX,而是生成一个script元素来请求服务器。由于浏览器不会阻止脚本元素的请求,因此请求可以到达服务器。服务器收到请求后,响应一段JS代码。这段代码其实是一个函数调用,调用客户端预先生成的函数,将浏览器需要的数据作为参数传递给函数,从而间接向客户端传输数据JSONP有明显的缺点,即,只能支持GET请求的跨域解决方案3-CORS概述CORS是基于http1.1的跨域解决方案,全称是Cross-OriginResourceSharing,跨域资源共享。它的大致思路是:如果浏览器想要跨域访问服务器的资源,就需要获得服务器的许可。要知道一个请求是可以携带很多信息的,这会对服务器造成不同程度的影响。例如,一些请求只是得到一些消息,有些请求会改变服务器的数据。针对不同的请求,CORS规定了三种不同的交互模式,即:需要预校验的简单请求、带身份凭证的请求,这三种模式是自上而下递进的,请求可以做的事情越来越多,要求也越来越严.下面分别介绍三种请求方式的具体规范。简单请求当浏览器运行一段ajax代码时(无论是使用XMLHttpRequest还是fetchapi),浏览器首先会判断它属于哪种请求模式。简单请求的判定详见前端进阶面试题解答。当请求同时满足以下条件时,浏览器会认为它是一个简单请求:请求方法属于以下之一:getposthead请求头只包含安全字段,常见的安全字段如下:AcceptAccept-LanguageContent-LanguageContent-TypeDPRDownlinkSave-DataViewport-WidthWidthrequestheaderifContainsContent-Type,只有以下值之一:text/plainmultipart/form-dataapplication/x-www-form-urlencoded如果以上三个条件同时满足次,浏览器判断为简单请求。下面是一些例子://简单请求fetch('http://crossdomain.com/api/news');//请求方式不符合要求,不是简单请求fetch('http://crossdomain.com/api/news',{method:'PUT',});//添加了额外的请求头,而不是简单的请求fetch('http://crossdomain.com/api/news',{headers:{a:1,},});//简单请求fetch('http://crossdomain.com/api/news',{method:'post',});//content-type不符合要求,不是a简单请求获取('http://crossdomain.com/api/news',{method:'post',headers:{'content-type':'application/json',},});简单请求的交互规范当浏览器判断一个ajax跨域请求是一个简单请求时,会发生以下事情:Origin字段会自动添加到请求头中。例如,页面http://my.com/index.html中的以下代码会导致跨域//简单请求获取('http://crossdomain.com/api/news');请求发送后,请求头格式如下:GET/api/news/HTTP/1.1Host:crossdomain.comConnection:keep-alive...Referer:http://my.com/index.htmlOrigin:http://my.com看到最后一行了吗,Origin字段会告诉服务器哪个源地址应该包含Access-Control-Allow-Origin当服务器收到请求时,如果请求允许跨域访问,你需要在响应头中添加Access-Control-Allow-Origin字段。该字段的值可以是:*:表示我是开放的,我对任何人开放允许访问特定来源:比如http://my.com,表示我允许你访问。其实这两个值对于客户端http://my.com来说是一样的,因为客户端不会管理其他源服务如果服务器不被允许,你只关心自己是否被允许。当然,服务器也可以维护一个允许的来源列表。如果请求的来源命中列表,它将响应*或特定来源。为了避免后续的麻烦,强烈建议响应具体的来源假设服务器响应如下:HTTP/1.1200OKDate:Tue,21Apr202008:03:35GMT...Access-Control-Allow-Origin:http://my.com...message当浏览器看到服务器允许它访问body中的数据时,高兴得像个两百斤的孩子,于是传递了res??ponse以js顺利完成后续操作。下图简要描述了整个交互过程Requeststhatrequirepreflight简单的请求对服务器的威胁不大,所以允许完成上述简单的交互。但是,如果浏览器认为这不是一个简单的请求,它会遵循以下流程:浏览器发送Preflight请求,询问服务器是否允许,服务器允许浏览器发送一个真正的请求,服务器完成一个请求。真正的回应。例如,页面http://my.com/index.html中的以下代码会导致跨域请求//需要进行预检fetch('http://crossdomain.com/api/user',{method:'POST',//postrequestheaders:{//设置请求头a:1,b:2,'content-type':'application/json',},body:JSON.stringify({name:'袁小津',age:18}),//设置请求体});如果浏览器发现不是简单的请求,就会按照下面的流程与服务器交互,发送预检请求请求服务器允许OPTIONS/api/userHTTP/1.1Host:crossdomain.com...Origin:http://my.comAccess-Control-Request-Method:POSTAccess-Control-Request-Headers:a,b,content-type可以看出这不是我们要发送的真正请求,request不包含我们的请求头,也没有消息体。这是一个预检请求,其目的是询问服务器是否允许后续的真实请求。preflightrequest没有requestbody,里面包含了后续真实请求中要做的事情。预检请求具有以下特点:请求方式为OPTIONS,没有请求体。请求头中包含Origin:请求的来源,与简单请求意义相同。Access-Control-Request-Method:后续真实请求会使用的请求方式收到预检请求后,如果允许此类请求,则需要响应以下消息格式HTTP/1.1200OKDate:Tue,21Apr202008:03:35GMT...Access-Control-Allow-Origin:http://my.comAccess-Control-Allow-Methods:POSTAccess-Control-Allow-Headers:a,b,content-typeAccess-Control-Max-Age:86400...对于预检请求,无需响应对任何消息体,只在响应头中添加:Access-Control-Allow-Origin:和简单请求一样,表示允许的来源Access-Control-Allow-Methods:表示允许的后续真实请求方法Access-Control-Allow-Headers:表示允许更改的请求头Access-Control-Max-Age:告诉浏览器r,在多少秒内,对于相同的请求源、方法、header,不需要发送预检请求。浏览器发送真实请求并且服务器允许预检后,浏览器会发送真实请求后,上面的代码会生成如下请求数据POST/api/userHTTP/1.1Host:crossdomain.comConnection:keep-alive...Referer:http://my.com/index.htmlOrigin:http://my.com{"name":"xiaoming","age":18}服务器响应真实请求HTTP/1.1200OK日期:2020年4月21日星期二08:03:35GMT...访问控制-允许-来源:http://my.com...可以看到预检完成后,后面的处理和简单请求一样。下图简要描述了整个交互过程。跨域请求没有自带cookie,导致一些需要权限的操作无法进行,但是可以通过简单的配置实现//xhrvarxhr=newXMLHttpRequest();xhr.withCredentials=true;//获取apifetch(url,{credentials:'include',});这样,跨域的ajax请求就是一个带凭证的请求,当一个请求需要带上cookie时,无论是简单请求还是预先设置好的Inspection请求都会在请求头中添加一个cookie字段,而当服务器响应,需要明确告知客户端:服务器允许这样的凭据通知的方式也很简单,只需在响应头中添加:Access-Control-Allow-Credentials即可:true表示对于带有身份的请求certificate,如果服务器没有明确通知,浏览器仍然会认为是跨域拒绝。另外需要特别注意的是:对于带有身份证书的请求,服务器一定不能设置Access-Control-Allow-Origin的值为*。这就是不推荐*的原因。一个额外的补充。跨域访问时,JS只能获取到一些最基本的响应头,比如:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他的头,需要服务器设置此响应标头。Access-Control-Expose-Headersheader允许服务器将允许浏览器访问的headers列入白名单,例如:Access-Control-Expose-Headers:authorization,a,b,这样JS就可以访问指定的响应header。
