跨域

198 阅读10分钟

为什么有跨域?因为同源策略

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

同源的定义

如果两个 URL 的 protocolport  (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。

跨源网络访问

同源策略控制不同源之间的交互,例如在使用XMLHttpRequest 或 <img> 标签时则会受到同源策略的约束。这些交互通常分为三类:

  • 跨域写操作(Cross-origin writes) 一般是被允许的 例如链接(links),重定向以及表单提交。特定少数的HTTP请求需要添加 preflight
  • 跨域资源嵌入(Cross-origin embedding) 一般是被允许。如script,link,img,video,@font-face,@font-face.
  • 跨域读操作(Cross-origin reads) 一般是不被允许的 但常可以通过内嵌资源来巧妙的进行读取访问。例如,你可以读取嵌入图片的高度和宽度,调用内嵌脚本的方法,或availability of an embedded resource.

跨域简介

同源策略是浏览器的行为。 浏览器对跨域的默认限制策略:

  • cookie、localStorage和IndexDB无法获取
  • DOM和JS对象无法获得
  • AJAX请求的响应不能接收到。 XMLHttpRequestFetch这两个API都部署了同源限制。

跨域通常发生在浏览器的运行时阶段,具体来说,是在浏览器发送请求之前和接收响应之后的阶段中。

在浏览器发送请求之前,浏览器会检查该请求的目标地址是否与当前网页所在的域名相同。如果不相同,则会触发跨域检测机制,这个机制会阻止脚本继续执行。

在接收响应之后,浏览器会对响应进行解析和处理,但在处理过程中,如果发现响应的内容与当前网页的域名不一致,也会触发跨域检测机制,这个机制同样会阻止浏览器继续处理响应。 无效的跨域访问得不到响应数据是被浏览器拦截,而非服务端。简单跨域请求可以成功执行

允许跨域资源访问

图片探测

原理

利用<img>标签实现跨域通信最早的一种技术。任何页面都可以跨域加载图片而不必担心限制,因此这也是在线广告跟踪的主要方式。可以动态创建图片,浏览器通过图片探测拿不到任何数据,但可以通过监听onload和onerror事件处理程序得知什么时候收到响应。 图片探测是与服务器之间简单、跨域、单向的通信。数据通过查询字符串发送,响应可以随意设置,不过一般是位图图片或者值为204的状态码。

用途

用于跟踪用户在页面上的点击操作或动态显示广告。

缺点

  • 只能发送get请求;
  • 无法获取服务器响应的内容。

JSONP(json+padding)

jsonp格式包含两个部分:回调和数据。

原理

由于同源策略的限制,XMLHttpRequest只允许请求当前源(协议,域名,端口)的资源。为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出json数据并执行回调函数,解决跨域的数据请求。 动态插入script标签,通过script标签引入一个js文件,这个js文件载入成功后执行我们在URL参数中指定的函数,并且会把我们需要的json数据作为参数传入。

实现

function createJs(sUrl){
// 创建scrip元素
var oScript =document.createElement('script');
// 添加type和src
oScript.type = 'text/javascript';

oScript.src= sUrl;
// 插入页面
document.getElementsByTagName('head')[0].appendChild(oScript);

}

createJs('jsonp.js');

box({

'name': 'test'

});

function box(json){

console.log(json.name);

}

优缺点

  • 优点:
    • 兼容性好
    • 简单易用,
    • 可以直接访问响应,支持浏览器与服务器双向通信
  • 缺点:
    • 只支持get请求;
    • 从不同域拉取可执行代码。如果这个域并不可信,只能完全删除jsonp
    • 不好确定jsonp请求是否失败。

CORS(Cross-Origin-Resource-Share,跨域资源共享)

跨源资源共享 (CORS)(或通俗地译为跨域资源共享)是一种基于 HTTP头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。 CORS 是 HTTP 的一部分,它允许服务端来指定哪些主机可以从这个服务端加载资源。主要通过Access-Control-Allow-Origin实现。如果浏览器检测到相应的设置,就可以允许ajax进行跨域访问。

  • Access-Control-Allow-Origin:*;
  • Access-Control-Allow-Methods:*;
  • Access-Control-Allow-Credentials:true;
  • Access-Control-Allow-Headers:*; 需要浏览器和后端同时支持。浏览器会自动进行CORS通信,实现CORS通信的关键是后端。
    通过cors解决跨域问题,会在发送请求时出现两种情况,分别为简单请求和复杂请求。 以AJAX为例,当满足以下条件时,会触发
  • 简单请求
    • 方法:get、head、post
    • Content-Type:text/plain、multipart/form-data、application/x-www-form-urlencoded
    • 请求中任意XMLHttpRequestUpload对象均没有注册任何事件监听器。XMLHttpRequestUpload对象跨域使用XMLHttpRequest.upload属性访问。
    • 请求中没有使用 ReadableStream对象。
  • 复杂请求 首先会通过options方法,通过该请求可以知道服务端是否允许跨域请求。如果响应支持跨域,则继续发出正式请求,不支持的话,会在控制台显示错误,正式请求会被浏览器在发送之前拦截。 options请求就是预检请求,可用于检测服务器允许的http方法。当发起跨域请求时,由于安全原因,触发一定条件时浏览器会在正式请求之前自动发起OPTIONS请求,即cors预检请求,服务器如果接受该跨域请求,浏览器才继续发起正式请求。

原因:跨域共享标准规范要求对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME类型 的 POST请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP认证相关数据)。

  • options请求优化 发起跨域请求时,简单请求只发起一次,复杂请求需要2次。触发预检时,跨域请求会发送两次请求次数,延迟请求真正发起的时间,会严重影响性能。 优化options的两种方法:
    • 用其他的跨域方式做跨域请求,将复杂请求转为简单请求,如jsonp
    • 对options请求进行缓存。服务端设置*Access-Control-Max-Age*用来指定本次预检请求的有效期。浏览器会根据返回的Access-Control-Max-Age字段缓存第一次options预检请求的响应结果。在缓存有效期内,该资源的请求(url和header字段都相同的情况下)不会再触发预检。

修改document.domain跨子域

原理

将子域和主域的document.domain设为同一个主域。 前提条件:这两个域名必须属于同一个基础域名而且所用的协议,端口都要一致。

使用

使用 document.domain 来允许子域安全访问其父域时,需要在父域和子域中设置 document.domain 为相同的值。

location.hash

片段标识符(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.name跨域

window对象有个name属性。name属性的一个特征是:在同一个window的生命周期内,载入的所有页面都是共享一个window.name的,每个页面对window.name都有读写的权限。window.name是持久存在于一个窗口载入过的所有页面中的。

h5的window.postMessage方法跨域

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机  (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。

使用(还有问题)

function receiveMessage(event)
{
  var origin = event.origin
  if (origin !== "https://juejin.cn/")
    return;
  else{
      console.log('hi,mdn')
  }
  // ...
}
let otherWindow=window.open('https://juejin.cn/')
let msg={
    name:'coco'
}
otherWindow.postMessage(msg,'https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage',false)



//VM910:19 Uncaught TypeError: Failed to execute 'postMessage' on 'Window': The provided value cannot be converted to a sequence

nginx反向代理

正向代理与反向代理的区别是:正向代理对浏览器负责,反向代理对服务器负责。

原理

服务器通信没有同源策略的限制。

proxy

Proxy的原理是通过一个同域的Web服务来接收前端的AJAX请求,然后将其转发到目标Web服务上。

websocket

Websocket 通过HTTP/1.1 协议的101状态码进行握手。 WebSocket协议端口是80。WebSocket SSL协议端口是443。

原理

来源未知
websocket服务器有origin字段,如果浏览器的origin在服务器的白名单里,就可以通信。

阻止跨域资源访问

  • 阻止跨域写操作,只要检测请求中的一个不可推测的标记(CSRF token)即可,这个标记被称为 Cross-Site Request Forgery (CSRF) 标记。使用这个标记来阻止页面的跨站读操作。
  • 阻止资源的跨站读取,需要保证该资源是不可嵌入的。
  • 阻止跨站嵌入,需要确保你的资源不能通过以上列出的可嵌入资源格式使用。 当您的资源不是网站的入口点时,还可以使用CSRF令牌来防止嵌入。

限制范围

除了限制Cookie的访问外,同源策略的安全限制还包含如下3个方面:

  • LocalStorage、SessionStorage和IndexDB无法读取;
  • DOM对象模型无法获取;
  • AJAX请求不能发送。

参考资料