Eisen's Blog

© 2023. All rights reserved.

HTTP 请求中有关跨域的总结

March 07, 2013

ajaxhttpsecurityweb

以前一直没有好好的看过相关的只是,只是一知半解,之前在百度的时候知道 iframe 中的东西如果 domain 与 iframe 外的不一致,那么就不能用 js 去操作里面的对象。window.open 新开的东西也同样无法操作。但是具体的策略如何就说不清楚了,最近看看了这方面的东西,总结一下。

这部分的知识主要源自于 MDN W3 以及另外一篇不错的文章 Same Origin Policy Part 1: No Peeking

首先介绍一下两个名词

  • SOP:Same Origin Policy
  • CORS:Cross-Origin Resource Sharing

然后从 SOP 说起,首先看看 MDN 上面的对于 同域 的定义

The same-origin policy restricts how a document or script loaded from one origin can interact with a resource from another origin.

Two pages have the same origin if the protocol, port (if one is specified), and host are the same for both pages.

很简单把,origin 和 domain 的意义是差不多的。在 chrome 的调试工具中可以输入

document.domain

获取。端口与协议的一致性也要考虑在内。

那么,在遇到跨域的问题时浏览器会怎么处理呢?通常会遵循如下的原则:

  • Cross-origin writes are typically allowed. Examples are links, redirects and form sumissions. Certain arely used HTTP requests require preflight.
  • Cross-origin embedding is typically allowed. Examples are listed below.
  • Cross-origin reads are typically not allowed, but read access it often leaked by embedding. For example you can read the width and height of an embedded image, the actions of an embedded script, or the availability of an embedded resource.

看到的那篇文章中把这些问题按照 linux 系统中的三个权限对应在 read write execute (rwx)。翻译过来就是

  • R 读被完全禁止
  • W 允许写
  • X 允许执行

在进一步的阐明如下

  • 任何企图获取异域资源内容的请求多会被禁止

    这里所知的读行为其实是一个很宽泛的概念,指的是任何具备读操作潜力的行为。比如,我任何形式的 ajax 操作都可以获取返回的请求内容,获取了内容相当于我是读成功了。那么,由于 SOP 的限制,所有的 ajax 方式的跨域请求都会被禁止。相应的,iframe 以及 window.open 的页面内部的一切都是无法访问的。

  • 写是可以接受的

    links, redirects and form sumissions 这些都算是写,其实有些晕,尤其是 Links Redirects 凭什么算是写呢?不知道。我觉得这两个应该算是读的范畴,但是同样的,它们做了页面的跳转,同样是没有办法从链接打开的页面或者是重定向的页面中取回任何资源到原来的页面。唯一可以称作是写的就只有表单提交了,当你提交了表单之后同样是会做跳转,原网页依然会失去控制权。不过允许远程表单提交会带来 CSRF 的问题。

  • 任何的执行都是允许的

    在页面可执行的差不多就是两种文件 css 以及 js。从任何域拿到的 css 以及 js 都可以在我们自己的网站上正常的使用

那么,当我们需要做跨域的请求的时候怎么办?

JSONP

jsonp 是只把你请求的资源做成一个可执行的资源,把读转化为执行。比如我要请求一个 json

  {"name": "aisensiy", "age": 24}

服务端把这个结果做成这样

  callbackFunction({"name": "aisensiy", "age": 24});

那么,你只需要把返回的结果作为一个 js 加载到页面中,想要的结果就有了。

一个不靠谱的方式: 修改 domain

刚刚看到一个大神的文章,https://ruby-china.org/topics/7598

这里需要说明的一点是,设置document.domain这种跨子域通讯的方式,不是说子域与子域之间可以发送AJAX请求,而是利用子域与子域的iframe之间可以传送数据这一特性完成跨域通讯。

在 MDN 有关 SOP 的文章中说到可以通过修改 document.domain 的方式可以打通各个子域名。例如一个页面 A 的 domain 为 'a.company.com',通过

document.domain = 'company.com';

可以访问 company.com 域下的内容。

我刚才做了尝试,失败了。ajax 请求总的 Origin 并没有变化,所以依然报错。而且通过修改 ajax request header 中的 origin 也是不允许的。那么,我认为这种方式应该是行不通了,我不知道为神马文档没有更新,也可能是我尝试的方式有误?

正式的执行跨域请求的方式

其实大多数浏览器与服务器已经支持了一套允许跨域访问的标准,Cross-Origin Resource Sharing。服务器可以通过设置HTTP头,添加 Access-Control-Allow-Origin 字段来告诉浏览器什么样子的跨域请求是允许的。 比如

Access-Control-Allow-Origin:*

表示任何域的请求都是可以被接受的。而

Access-Control-Allow-Origin:https://example.com

表明只有从 example.com 的请求是被允许的。 我用 node 做了一个简单的测试。开一个 node server,一个在端口 8000。 端口 8000 的程序如下

var http = require('http');
http.createServer(function(req, res) {
	res.writeHead(200, {
        'ContentType': 'text/plain',
        'Access-Control-Allow-Origin': 'https://localhost:3000'});
	res.end('Hello node\n');
}).listen(8000);

console.log('Server running...');

在端口 4000 开一个 node server 向 localhost:8000 请求。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  if (xhr.readState == 4) {
    if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
      alert(xhr.responseText);
    } else {
      alert(xhr.status);
    }
  }
};

xhr.open('GET', 'https://localhost:8000/');

结果如下

修改请求页面的端口到 3000,再次发送请求

请求被接受了。这种跨域的问题通常会在 做前后端分离的系统的时候用得到。比如 我所有的面向后端的请求全部集中在域 api.domain.com 而我的前端页面都在域 web.domain.com 下,那么在 server 端通过简单的添加允许的域就可以解决这个问题。

这里在补充一句,如果是需要允许多个域名的访问应该怎么做呢?我搜了下,发现一个 access-control-allow-origin-multiple-origin-domains 说明了这个问题:如果是多个域名的话,就需要server自己处理,如果发现请求在允许的列表中,就在 http 响应头中 Access-Control-Allow-Origin 字段设为这个域。

相关资源