当前位置: 首页 > Web前端 > HTML

深入理解跨域和最佳实践分享

时间:2023-03-29 11:50:08 HTML

跨域是前端开发中非常普遍的问题,尤其是随着单页面应用(SinglePageApplication,SPA)的兴起,分离开发和部署前后端之分,前端在本地开发部署的过程中会面临跨域问题。再来说说跨域的话题,以及项目中跨域的一些实践经验,希望能带来一些新的收获。什么是跨域?首先我们要了解什么是同源策略,在MDN中有介绍:同源策略是浏览器的一项重要的安全策略,用于限制源文档或脚本的加载方式可以与来自另一个来源的资源进行交互。它有助于阻止恶意文档并减少可能的攻击媒介。从这段话我们知道,同源策略是浏览器的安全策略,来源(Origin)是判断是否满足同源策略的条件。我们常说的跨域,准确的说应该是跨域,目的是操作其他来源的资源,不受同源策略的限制。比如我们在本地开发,源是http://localhost:3000,服务器接口地址是https://api.feishu.cn。这时候通过FetchAPI访问接口就会产生跨域。同源策略源于浏览器,但在非浏览器环境下,一般不会对同源策略做任何限制。例如在Node.js中,我们可以请求任何URL并获得结果。因此,基于Node.js的服务器可以直接跨域访问资源。当然我们也可以关闭浏览器的同源策略,关闭后也可以在浏览器环境下直接跨域。以Chrome为例,可以通过在Chrome中添加启动参数来关闭:$/path/to/chrome.app--disable-web-security。关闭后浏览器会有安全隐患。建议仅用于本地开发。源协议、域名(准确的说是主机名,因为除了域名也可以是IP)、端口号是共同确定的。三者完全一样。URL被认为是同源的。举几个例子:https://www.bytedance.com和https://jobs.bytedance.com,不同的域名,不同的来源http://www.bytedance.com和https://www.bytedance。com,不同的协议,不同的来源http://localhost和http://localhost:8080,不同的端口(80和8080),不同的来源特别是当我们直接打开一个HTML文件时,我们使用文件协议来请求HTTPresources当协议不同时,就会发生跨域。允许在有限范围内修改源代码。浏览器提供API修改子域名下的源为父域名的源。比如我们在https://open.feishu.cn下执行:document.domain='feishu.CN';此时页面的源变成了https://feishu.cn,满足同源策略,所以我们可以直接访问父域名下的资源。如果修改为非父域名(如bytedance.com),浏览器会报错。同源策略控制不同源之间的操作。这些操作通常分为三类:资源嵌入:通常允许。例如表示资源嵌入不受同源策略控制,而JSONP是利用该特性的跨域实现。

是资源写入,不受同源策略控制,所以当form的action不是同源URL时,也可以成功提交。XMLHttpRequest或FetchAPI是我们关注的重点。下面详细讨论一下:当发起HTTPGET请求(或其他类型)读取资源时,受同源策略控制。在不同来源的情况下,我们无法读取请求返回的内容。发起HTTPPOST请求修改资源时,一般不受同源策略控制(称为简单请求)。资源是允许修改的;对于非简单请求,由同源策略控制,不允许修改资源。需要注意的是,虽然HTTPGET读取资源受同源策略控制,请求发送成功,但是浏览器限制了。请求返回的内容没有给我们而是抛出错误。HTTPPOST写入资源时,简单请求也发送成功,所以资源修改成功。因此,可以说写操作不受同源策略控制,但是请求的返回值我们也拿不到(因为是读操作)。非简单请求采用先发送预校验请求的方式判断是否允许跨域。如果不是,则不会发送真正的请求,以避免资源修改。源策略控制。下面的实战部分将详细讨论如何允许跨域。WebSocket不受同源策略控制。简单请求必须满足以下条件:HTTP方法为:GETPOST或HEAD除了浏览器自动设置的字段(如Connection、User-Agent)外,请求头只允许以下字段:AcceptAccept-LanguageContent-LanguageContent-Type:onlyallowedvaluesaretext/plainmultipart/form-dataapplication/x-www-form-urlencodedRange:onlyallowedsimplerangeheadervalueslikebytes=256-orbytes=127-255不满足以上条件是一个非简单的请求。例如,当我们在请求头中添加X-JWT-Token或Content-Type:application/json时,这个请求就不是一个简单的请求了。为什么我们需要同源策略?上面我们说过,同源策略是一种安全策略。如果关闭同源策略,个人信息将存在安全风险。例如,恶意站点可以调用其他网站的用户信息接口,获取敏感信息,恶意站点可以调用点赞或删除其他网站的接口,恶意修改跨域资源等。最佳实践那么我们如何跨域进行呢?在同源策略的基础上,浏览器提出了一种安全的跨域机制,称为跨源资源共享(CrossOriginResourceSharing,CORS)。当然,在该机制发布之前,也有诸如JSONP等满足浏览器安全机制的跨域解决方案,本文不再赘述。CORS的使用非常简单。服务端(即请求的资源)在响应头中添加:Access-Control-Allow-Origin:https://www.bytedance.com,其中的值表示允许跨域访问Source,然后https://www.bytedance.com可以访问本服务器的资源。我们可以看到请求发送成功并得到响应,浏览器可以拿到Access-Control-Allow-Origin响应头并判断是否可以跨域(先请求再判断)。那么对于非简单请求,为了避免意外修改资源,需要先判断是否可以发起跨域修改请求(先判断再请求)。这时候浏览器会先发起preflight请求(HTTP方法为OPTIONS),获取服务器返回的Access-Control-Allow-Origin并判断,如果允许跨域则发起真正的请求。如果我们不能修改服务器端怎么办?我们可以实现一个我们可以控制的代理服务器,添加允许跨域响应头或者直接同域,解决跨域问题,比如通过Nginx、Charles或者webpack-dev-server代理。下面讨论一下项目中几种常见的跨域场景和最佳实践:跨域请求需要携带CookieXMLHttpRequest,FetchAPI在发起跨域请求时默认不携带服务端的源cookie,影响服务端的用户识别.我们需要添加参数,例如:constxhr=newXMLHttpRequest();xhr.withCredentials=true;或获取(网址,{凭证:'include',});浏览器会携带cookie,服务器需要返回响应头:Access-Control-Allow-Credentials:true浏览器会正常返回响应内容给我们,否则会抛出允许跨域请求的错误白名单中的源当有多个源需要允许跨域访问时可以配置Access-Control-Allow-Origin:*允许所有源的跨域访问,但开放范围过大存在一定的安全风险,在这种情况下,浏览器不允许cookie。我们需要对origins进行细粒度的控制,但是Access-Control-Allow-Origin不允许设置多个origins。我们可以通过在请求头Origin中加入白名单来动态返回Access-Control-Allow-Origin的值来解决问题。Origin是浏览器在进行跨域请求时自动携带的值,表示请求的来源。我们在服务端定义一个白名单来判断源是否在白名单中。如果是,则返回等于请求标头Origin的Access-Control-Allow-Origin的值。以express为例,代码如下:constwhiteList=['https://jobs.bytedance.com','https://www.bytedance.com'];app.get('/path/to/api',(request,response)=>{const{origin}=request.headers;if(whiteList.includes(origin)){response.header('Access-Control-Allow-Origin',origin);响应。header('Access-Control-Allow-Credentials',true);}});获取一些跨域请求返回的响应头。默认情况下,并不是所有跨域响应中的响应头我们都能获取到。默认浏览器只返回一些基本的Responseheaders,包括:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma当我们需要获取一些额外的responseheaders,比如X-TT-LogID来记录每个请求的logID时,我们需要让服务器返回响应头:Access-Control-Expose-Headers:OnlyX-TT-LogIDcanbegetting。使用简单的请求获取资源。前面我们了解到,非简单请求会先发起preflight请求检查是否允许跨域,所以需要两次HTTP请求来完成。操作。对于我们知道是读操作的请求,我们可以尽量满足简单请求的条件来减少preflight请求,从而减少请求时间。一般来说,我们需要注意的点有以下几点:读取资源时尽量使用GET或者POST请求。对于POST请求,需要通过body传递JSON数据,Content-Type使用text/plain代替application/json,服务端还需要支持将text/plain解析成JSON其他需要携带的信息request,尽量放在请求参数中,而不是自定义请求头,因为额外的请求头也会导致非简单的请求,比如JWT的X-JWT-TokenCDN资源也需要允许跨域才能访问到Sentry或Perfsee等前端监控平台,需要监控站点JS脚本错误,而这些JS脚本部署在CDN服务器上,由于CDN和我们站点往往是不同来源的,浏览器不会返回具体的错误信息嵌入了不同来源的都需要添加crossorigin属性:总结我们从三个角度讨论了什么是跨域,为什么需要同源策略,以及跨域的解决方案和实践。没有跨域限制。启动参数disable-web-security可以关闭浏览器的同源策略。可以用协议、域名、端口号三元组来判断同源。通过document.domain可以对简单和非简单请求的来源进行有限的修改。不同的是,非简单请求会先发起预检请求。同源策略保证了浏览器的安全。跨域是通过添加响应头Access-Control-Allow-Origin来解决的。一些跨域实现的最佳实践也可以借助代理来实现:跨域Domain请求使用Access-Control-Allow-Credentials来携带Cookies。使用请求头Origin允许来自白名单中的源的跨域请求。使用Access-Control-Expose-Headers获取跨域请求返回的某些响应头。获取资源,尽可能使用简单的请求。为了减少请求的耗时,CDN资源也需要允许跨域如果你有其他关于跨域的最佳实践欢迎分享