We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
同源、同域与同站
首先确认一点,同源是什么?
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。 同源的定义 如果两个 URL 的 protocol、port (en-US) (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。(“元组” 是指一组项目构成的整体,双重/三重/四重/五重/等的通用形式)。 下表给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例: URL 结果 原因 http://store.company.com/dir2/other.html 同源 只有路径不同 http://store.company.com/dir/inner/another.html 同源 只有路径不同 https://store.company.com/secure.html 失败 协议不同 http://store.company.com:81/dir/etc.html 失败 端口不同 ( http:// 默认端口是80) http://news.company.com/dir/other.html 失败 主机不同
那这个同源策略有啥用? 我们知道,HTML页面中的代码是可以引用其他服务的资源的。那么就要面临请求其他服务地址的情况,这时候同源策略就会来约束我们的请求了。
跨源网络访问 同源策略控制不同源之间的交互,例如在使用XMLHttpRequest 或 <img> 标签时则会受到同源策略的约束。这些交互通常分为三类: 跨源写操作(Cross-origin writes)一般是被允许的。例如链接(links),重定向以及表单提交。特定少数的HTTP请求需要添加 preflight。 跨源资源嵌入(Cross-origin embedding)一般是被允许(后面会举例说明)。 跨源读操作(Cross-origin reads)一般是不被允许的,但常可以通过内嵌资源来巧妙的进行读取访问。例如,你可以读取嵌入图片的高度和宽度,调用内嵌脚本的方法,或availability of an embedded resource. 以下是可能嵌入跨源的资源的一些示例: <script src="..."></script> 标签嵌入跨域脚本。语法错误信息只能被同源脚本中捕捉到。 <link rel="stylesheet" href="..."> 标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的 HTTP 头部 Content-Type 。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。 通过 <img> 展示的图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG,... 通过 <video> 和 <audio> 播放的多媒体资源。 通过 <object>、 <embed> 和 <applet> 嵌入的插件。 通过 @font-face 引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。 通过 <iframe> 载入的任何资源。站点可以使用 X-Frame-Options 消息头来阻止这种形式的跨域交互。
可以看到,绝大部分普通资源(css js img)的加载是天然支持跨源访问的。 这里有一点需要指出:跨源读操作(Cross-origin reads)一般是不被允许的,指的是请求可以发出去,资源可以被被下载到浏览器内部,但是我们的代码无法取得返回值。 下载发生了,但是最后一步读取的过程被阻止了,同源限制发生的时机就在这里。
那如何可以让原本不支持跨源访问的资源可以跨源访问呢?
如何允许跨源访问 可以使用 CORS 来允许跨源访问。CORS 是 HTTP 的一部分,它允许服务端来指定哪些主机可以从这个服务端加载资源。
如何阻止跨源访问
如何阻止跨源访问 阻止跨源写操作,只要检测请求中的一个不可推测的标记(CSRF token)即可,这个标记被称为 Cross-Site Request Forgery (CSRF) 标记。你必须使用这个标记来阻止页面的跨站读操作。 阻止资源的跨站读取,需要保证该资源是不可嵌入的。阻止嵌入行为是必须的,因为嵌入资源通常向其暴露信息。 阻止跨站嵌入,需要确保你的资源不能通过以上列出的可嵌入资源格式使用。浏览器可能不会遵守 Content-Type 头部定义的类型。例如,如果您在HTML文档中指定 <script> 标记,则浏览器将尝试将标签内部的 HTML 解析为JavaScript。 当您的资源不是您网站的入口点时,您还可以使用CSRF令牌来防止嵌入。
跨源脚本API访问 JavaScript 的 API 中,如 iframe.contentWindow、 window.parent、window.open 和 window.opener 允许文档间直接相互引用。当两个文档的源不同时,这些引用方式将对 Window 和 Location对象的访问添加限制,如下两节所述。 为了能让不同源中文档进行交流,可以使用 window.postMessage。 跨源数据存储访问 访问存储在浏览器中的数据,如 localStorage 和 IndexedDB,是以源进行分割。每个源都拥有自己单独的存储空间,一个源中的 JavaScript 脚本不能对属于其它源的数据进行读写操作。 Cookies 使用不同的源定义方式。一个页面可以为本域和其父域设置 cookie,只要是父域不是公共后缀(public suffix)即可。Firefox 和 Chrome 使用 Public Suffix List 检测一个域是否是公共后缀(public suffix)。Internet Explorer 使用其内部的方法来检测域是否是公共后缀。不管使用哪个协议(HTTP/HTTPS)或端口号,浏览器都允许给定的域以及其任何子域名(sub-domains) 访问 cookie。当你设置 cookie 时,你可以使用 Domain、Path、Secure、和 HttpOnly 标记来限定其可访问性。当你读取 cookie 时,你无法知道它是在哪里被设置的。 即使您只使用安全的 https 连接,您看到的任何 cookie 都有可能是使用不安全的连接进行设置的。
上述MDN的描述告诉了我们同源策略影响到了一些大概方面,总结下:
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。
同源策略限制了我们对上面3个场景的操作,但是有时候还是需要依赖他们做一些事情的,这时候我们要怎么让他们支持跨源?
Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。 举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享Cookie。 document.domain = 'example.com'; 现在,A网页通过脚本设置一个 Cookie。 document.cookie = "test1=hello"; B网页就可以读到这个 Cookie var allCookie = document.cookie; 注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。 另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。 Set-Cookie: key=value; domain=.example.com; path=/ 这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。
可以看到,cookie有自己的同源规则,并不完全跟浏览器对其他资源的同源规则一样。它只要求 一域名+二级域名 一致即可,就可以满足同源策略,然后2个应用就可以共享这个cookie了。这也是我们常说的 cookie 同站。只要 一级域名+二级域名 这个条件满足,我们就说2个地址同站,可以共享cookie。否则就是跨站。 然后浏览器还给我们提供支持把 子域名 修改到 父域名 的操作,如 w1.example.com -> example.com 上面的操作告诉我们 : 子域名 www.host1.com 可以读取 其他 子域名的 w.host1.com 通过改写自己的domain后给 host1.com 设置的cookie。也就是说 www.host1.com 可以读取 父域名 host1.com下存储的cookie。 但是 www.host1.com 和 w.host1.com 直接无法相互读取。 且浏览器不允许 把 父domain 设置为 子domain。
cookie可以部分突破同源限制,只有符合同站要求即可。但是 LocalStorage 和 IndexDB 这2个完全是同源要求,必须满足浏览器的同源要求才可以共享。
2:跨源DOM操作受限
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。 比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。 document.getElementById("myIFrame").contentWindow.document // Uncaught DOMException: Blocked a frame from accessing a cross-origin frame. 上面命令中,父窗口想获取子窗口的DOM,因为跨源导致报错。 反之亦然,子窗口获取主窗口的DOM也会报错。 window.parent.document.body // 报错 如果两个窗口一级域名相同,只是二级域名不同,那么设置上一节介绍的document.domain属性,就可以规避同源政策,拿到DOM。 对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。 片段识别符(fragment identifier) window.name 跨文档通信API(Cross-document messaging)
主要是2个有关系的窗口,如何 窗口A和A通过open打开的窗口,窗口A和它内部的跨源iframe之间有时候存在需要进行DOM操作的场景
2.1 片段识别符 片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://example.com/x.html#fragment的#fragment。如果只是改变片段标识符,页面不会重新刷新。 父窗口可以把信息,写入子窗口的片段标识符。 var src = originURL + '#' + data; document.getElementById('myIFrame').src = src; 子窗口通过监听hashchange事件得到通知。 window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // ... } 同样的,子窗口也可以改变父窗口的片段标识符。 parent.location.href= target + "#" + hash;
这个主要利用浏览器给我们提供的支持,虽然大部分跨源window属性的访问都受限,但是至少这个location的读写还是保留下来了。
浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。 父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性。 window.name = data; 接着,子窗口跳回一个与主窗口同域的网址。 location = 'http://parent.url.com/xxx.html'; 然后,主窗口就可以读取子窗口的window.name了。 var data = document.getElementById('myFrame').contentWindow.name; 这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。
这个方案还是第一次知道,有点意思。 MDN对此的描述是:
窗口的名字主要用于为超链接和表单设置目标(targets)。窗口不需要有名称。 在某些框架里(如,SessionVars 和 Dojo's dojox.io.windowName ,该属性也被用于作为 JSONP 的一个更安全的备选,来提供跨域通信(cross-domain messaging)。现代 web 应用应使用 postMessage API 进行敏感的跨域通信。 window.name 会调用 toString 将赋给它的值转换成对应的字符串表示。
2.3 window.postMessage 上面两种方法都属于破解,HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。 这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。 举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。 var popup = window.open('http://bbb.com', 'title'); popup.postMessage('Hello World!', 'http://bbb.com'); postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。 子窗口向父窗口发送消息的写法类似。 window.opener.postMessage('Nice to see you', 'http://aaa.com'); 父窗口和子窗口都可以通过message事件,监听对方的消息。 window.addEventListener('message', function(e) { console.log(e.data); },false); message事件的事件对象event,提供以下三个属性。 event.source:发送消息的窗口 event.origin: 消息发向的网址 event.data: 消息内容 下面的例子是,子窗口通过event.source属性引用父窗口,然后发送消息。 window.addEventListener('message', receiveMessage); function receiveMessage(event) { event.source.postMessage('Nice to see you!', '*'); } event.origin属性可以过滤不是发给本窗口的消息。 window.addEventListener('message', receiveMessage); function receiveMessage(event) { if (event.origin !== 'http://aaa.com') return; if (event.data === 'Hello World') { event.source.postMessage('Hello', event.origin); } else { console.log(event.data); } }
同源政策规定,AJAX请求只能发给同源的网址,否则就报错。 这句话应该修正一下:简单请求确实是发出了,服务器也做出了资源响应,只不过 我们的代码读取的时候被阻止了,且浏览器报错了。非简单请求,浏览器不会直接发送原始请求,因为万一你数据很大然后还无法通过跨源验证,那我还 傻乎乎的下载下来最后再拦截你读取岂不是显得我浏览器很呆?所以就有了下面介绍的预请求机制。
除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。
JSONP WebSocket CORS
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。 它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。 首先,网页动态插入<script>元素,由它向跨源网址发出请求。 function addScriptTag(src) { var script = document.createElement('script'); script.setAttribute("type","text/javascript"); script.src = src; document.body.appendChild(script); } window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); } function foo(data) { console.log('Your public IP address is: ' + data.ip); }; 上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。 服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。 foo({ "ip": "8.8.8.8" }); 由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
JSONP利用script标签可以加载跨源的JS资源且执行的特性,利用事先预定好的函数声明和实现,通过get请求的参数让返回的文件代码去执行这个函数名+外部数据,从而实现 本源代码+外源数据 的业务功能
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。 下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自维基百科)。 GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com 上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。 正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。 HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
ws协议暂时不深入研究,之后再深入分析
跨源资源共享(CORS) 跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种基于HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的"预检"请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。 什么情况下需要 CORS ? 这份 cross-origin sharing standard 允许在下列场景中使用跨站点 HTTP 请求: 前文提到的由 XMLHttpRequest 或 Fetch 发起的跨源 HTTP 请求。 Web 字体 (CSS 中通过 @font-face 使用跨源字体资源),因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。 WebGL 贴图 使用 drawImage 将 Images/video 画面绘制到 canvas 功能概述 跨源资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。 CORS请求失败会产生错误,但是为了安全,在JavaScript代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。 接下来的内容将讨论相关场景,并剖析该机制所涉及的 HTTP 首部字段。 简单请求 某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求满足所有下述条件,则该请求可视为“简单请求”: 使用下列方法之一: GET HEAD POST 除了被用户代理自动设置的首部字段(例如 Connection ,User-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为: Accept Accept-Language Content-Language Content-Type (需要注意额外的限制) Content-Type 的值仅限于下列三者之一: text/plain multipart/form-data application/x-www-form-urlencoded 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。 请求中没有使用 ReadableStream 对象。
可以看到,简单请求确实要求请求本身的格式比较简单,且给出了强制的要求。不满足要求的请求需要先发送一个预请求,通过后才发送原始请求。 关于 XMLHttpRequest.upload 属性几乎没用过,暂且不研究。 请求中没有使用 ReadableStream 对象指的应该是 fetch 中我们是否使用 res 的可读流,因为流是不停的读取数据,在这个读取的过程中,我们的代码是可以参与其中, 这如果被允许的话,那等到资源加载完成的时候发现同源校验没通过,岂不是完蛋了?所以如果我们依赖了res的可读流,意味着必须要先通过预请求通过同源校验。
而且简单请求我们发现 跟表单提交的情况几乎一样:
这是为了兼容表单(form),因为历史上表单一直可以发出跨域请求。AJAX 的跨域设计就是,只要表单可以发,AJAX 就可以直接发。
下面是响应用来控制跨域的头部字段:
HTTP 响应首部字段 本节列出了规范所定义的响应首部字段。上一小节中,我们已经看到了这些首部字段在实际场景中是如何工作的。 Access-Control-Allow-Origin 响应首部中可以携带一个 Access-Control-Allow-Origin 字段,其语法如下: Access-Control-Allow-Origin: <origin> | * 其中,origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。 例如,下面的字段值将允许来自 http://mozilla.com 的请求: Access-Control-Allow-Origin: http://mozilla.com 如果服务端指定了具体的域名而非“*”,那么响应首部中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。 Access-Control-Expose-Headers 译者注:在跨源访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。 Access-Control-Expose-Headers 头让服务器把允许浏览器访问的头放入白名单,例如: Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header 这样浏览器就能够通过getResponseHeader访问X-My-Custom-Header和 X-Another-Custom-Header 响应头了。 Access-Control-Max-Age Access-Control-Max-Age 头指定了preflight请求的结果能够被缓存多久,请参考本文在前面提到的preflight例子。 Access-Control-Max-Age: <delta-seconds> delta-seconds 参数表示preflight请求的结果在多少秒内有效。 Access-Control-Allow-Credentials Access-Control-Allow-Credentials 头指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。当用在对preflight预检测请求的响应中时,它指定了实际的请求是否可以使用credentials。请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。 Access-Control-Allow-Credentials: true 上文已经讨论了附带身份凭证的请求。 Access-Control-Allow-Methods Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。 Access-Control-Allow-Methods: <method>[, <method>]* 相关示例见这里。 Access-Control-Allow-Headers Access-Control-Allow-Headers 首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。 Access-Control-Allow-Headers: <field-name>[, <field-name>]*
下面是请求发起方申请跨域支持的头部字段:
HTTP 请求首部字段 本节列出了可用于发起跨源请求的首部字段。请注意,这些首部字段无须手动设置。 当开发者使用 XMLHttpRequest 对象发起跨源请求时,它们已经被设置就绪。 Origin Origin 首部字段表明预检请求或实际请求的源站。 Origin: <origin> origin 参数的值为源站 URI。它不包含任何路径信息,只是服务器名称。 Note: 有时候将该字段的值设置为空字符串是有用的,例如,当源站是一个 data URL 时。 注意,在所有访问控制请求(Access control request)中,Origin 首部字段总是被发送。 Access-Control-Request-Method Access-Control-Request-Method 首部字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。 Access-Control-Request-Method: <method> 相关示例见这里。 Access-Control-Request-Headers Access-Control-Request-Headers 首部字段用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器。 Access-Control-Request-Headers: <field-name>[, <field-name>]*
总结下:
同源的意义我们清楚了,对应非同源我们称之为 跨源。
那网上大家说的跨域是是指? 域 这个词本身指的应该是 域名,host。跨域严格来说是 指的2个服务的域名不同。但是网上大家一般说的跨域访问其实指的 就是 跨源访问。因为跨域了必定已经跨源了。而浏览器是针对跨源做了限制,所以当被人问我们跨域问题的时候,其实指的是 跨源问题。
那跨站? 跨站是专门针对cookie的。 只要 一级域名+二级域名 相同,就是同站。 cookie的字段中有一些特殊字段是用来限制cookie的使用条件的,具体字段分析我们再单独写一个关于cookie的文章。
我们现在在这里只需要捋清楚 :
可以看到,条件在逐渐放宽。
而常见的跨域解决方案有如下一些:
跨域解决方案 1、 通过jsonp跨域 2、 document.domain + iframe跨域 3、 location.hash + iframe 4、 window.name + iframe跨域 5、 postMessage跨域 6、 跨域资源共享(CORS) 7、 nginx代理跨域 8、 nodejs中间件代理跨域 9、 WebSocket协议跨域
参考资料:
The text was updated successfully, but these errors were encountered:
No branches or pull requests
同源、同域与同站
首先确认一点,同源是什么?
那这个同源策略有啥用?
我们知道,HTML页面中的代码是可以引用其他服务的资源的。那么就要面临请求其他服务地址的情况,这时候同源策略就会来约束我们的请求了。
可以看到,绝大部分普通资源(css js img)的加载是天然支持跨源访问的。
这里有一点需要指出:跨源读操作(Cross-origin reads)一般是不被允许的,指的是请求可以发出去,资源可以被被下载到浏览器内部,但是我们的代码无法取得返回值。
下载发生了,但是最后一步读取的过程被阻止了,同源限制发生的时机就在这里。
那如何可以让原本不支持跨源访问的资源可以跨源访问呢?
如何阻止跨源访问
上述MDN的描述告诉了我们同源策略影响到了一些大概方面,总结下:
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。
同源策略限制了我们对上面3个场景的操作,但是有时候还是需要依赖他们做一些事情的,这时候我们要怎么让他们支持跨源?
可以看到,cookie有自己的同源规则,并不完全跟浏览器对其他资源的同源规则一样。它只要求 一域名+二级域名 一致即可,就可以满足同源策略,然后2个应用就可以共享这个cookie了。这也是我们常说的 cookie 同站。只要 一级域名+二级域名 这个条件满足,我们就说2个地址同站,可以共享cookie。否则就是跨站。
然后浏览器还给我们提供支持把 子域名 修改到 父域名 的操作,如 w1.example.com -> example.com
上面的操作告诉我们 : 子域名 www.host1.com 可以读取 其他 子域名的 w.host1.com 通过改写自己的domain后给 host1.com 设置的cookie。也就是说 www.host1.com
可以读取 父域名 host1.com下存储的cookie。 但是 www.host1.com 和 w.host1.com 直接无法相互读取。
且浏览器不允许 把 父domain 设置为 子domain。
cookie可以部分突破同源限制,只有符合同站要求即可。但是 LocalStorage 和 IndexDB 这2个完全是同源要求,必须满足浏览器的同源要求才可以共享。
2:跨源DOM操作受限
主要是2个有关系的窗口,如何 窗口A和A通过open打开的窗口,窗口A和它内部的跨源iframe之间有时候存在需要进行DOM操作的场景
这个主要利用浏览器给我们提供的支持,虽然大部分跨源window属性的访问都受限,但是至少这个location的读写还是保留下来了。
这个方案还是第一次知道,有点意思。
MDN对此的描述是:
同源政策规定,AJAX请求只能发给同源的网址,否则就报错。
这句话应该修正一下:简单请求确实是发出了,服务器也做出了资源响应,只不过
我们的代码读取的时候被阻止了,且浏览器报错了。非简单请求,浏览器不会直接发送原始请求,因为万一你数据很大然后还无法通过跨源验证,那我还
傻乎乎的下载下来最后再拦截你读取岂不是显得我浏览器很呆?所以就有了下面介绍的预请求机制。
除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。
JSONP
WebSocket
CORS
JSONP利用script标签可以加载跨源的JS资源且执行的特性,利用事先预定好的函数声明和实现,通过get请求的参数让返回的文件代码去执行这个函数名+外部数据,从而实现
本源代码+外源数据 的业务功能
ws协议暂时不深入研究,之后再深入分析
可以看到,简单请求确实要求请求本身的格式比较简单,且给出了强制的要求。不满足要求的请求需要先发送一个预请求,通过后才发送原始请求。
关于 XMLHttpRequest.upload 属性几乎没用过,暂且不研究。
请求中没有使用 ReadableStream 对象指的应该是 fetch 中我们是否使用 res 的可读流,因为流是不停的读取数据,在这个读取的过程中,我们的代码是可以参与其中,
这如果被允许的话,那等到资源加载完成的时候发现同源校验没通过,岂不是完蛋了?所以如果我们依赖了res的可读流,意味着必须要先通过预请求通过同源校验。
而且简单请求我们发现 跟表单提交的情况几乎一样:
下面是响应用来控制跨域的头部字段:
下面是请求发起方申请跨域支持的头部字段:
总结下:
同源的意义我们清楚了,对应非同源我们称之为 跨源。
那网上大家说的跨域是是指?
域 这个词本身指的应该是 域名,host。跨域严格来说是 指的2个服务的域名不同。但是网上大家一般说的跨域访问其实指的
就是 跨源访问。因为跨域了必定已经跨源了。而浏览器是针对跨源做了限制,所以当被人问我们跨域问题的时候,其实指的是
跨源问题。
那跨站?
跨站是专门针对cookie的。
只要 一级域名+二级域名 相同,就是同站。
cookie的字段中有一些特殊字段是用来限制cookie的使用条件的,具体字段分析我们再单独写一个关于cookie的文章。
我们现在在这里只需要捋清楚 :
可以看到,条件在逐渐放宽。
而常见的跨域解决方案有如下一些:
跨域解决方案
1、 通过jsonp跨域
2、 document.domain + iframe跨域
3、 location.hash + iframe
4、 window.name + iframe跨域
5、 postMessage跨域
6、 跨域资源共享(CORS)
7、 nginx代理跨域
8、 nodejs中间件代理跨域
9、 WebSocket协议跨域
参考资料:
The text was updated successfully, but these errors were encountered: