检查是否适用同源策略

25

在实际尝试使用ajax方法之前,是否有一种“安全”的方式检查URL是否适用于同源策略?这是我所拥有的:

function testSameOrigin(url) {

    var loc = window.location,
        a = document.createElement('a');

    a.href = url;

    return a.hostname == loc.hostname &&
           a.port == loc.port &&
           a.protocol == loc.protocol;
}

这种方法有点靠猜测,主要是基于维基百科的文章。有没有更好的预先检查跨域允许的方法?可以使用jQuery。


如果您进行跨域请求,它将失败,并显示 readystate=4statuscode=0,这与被中止的请求类似。 既然您无论如何都需要防范被中止的请求,为什么还需要进行此检查呢?我觉得这项安全措施是由外部强制执行的,您对此无法控制,因此任何在环境内部进行的检查都是错误的。所以我认为您永远不应该进行此检查,只需让请求失败即可。 - Halcyon
5个回答

16

在实际使用Ajax方法之前,有没有一种“安全”的方式来检查同源策略是否适用于某个URL?这是我的代码:

function testSameOrigin(url) {

    var loc = window.location,
        a = document.createElement('a');

    a.href = url;

    return a.hostname == loc.hostname &&
           a.port == loc.port &&
           a.protocol == loc.protocol;
}

这是一种安全可靠的方法,前提是你不要做(或者说不要做)某些事情。

这种方式有点像手工猜测,基于维基百科文章所述。

在“正常”的情况下,这应该完全可行。如果你计划使用跨域脚本,则需要进行修改。

如果你在脚本中修改了document.domain,例如从“foo.example.com”和“bar.example.com”到“example.com”,则你的testSameOrigin函数将对“http://example.com”返回false,实际上它应该返回true

如果你计划修改document.domain,可以在你的脚本中添加一个检查。

如果你计划使用CORS(参见上面的链接)来允许跨域通信,则它也会返回错误结果。但是如果你使用CORS,你将有一个可以通信的域名列表,你可以将该列表添加到该函数中。

有没有更好的预先检查跨域允许的方法?可以使用jQuery。

也许没有,尽管值得一提的是,你从Steve的答案中在控制台看到的可能是“观察者谬论”... 这些错误看起来像是控制台试图检查另一个窗口而不是脚本产生的。

假设你没有搞乱document.domain或使用CORS,则你原来的解决方案可能更好,因为它不需要进行额外的请求来确定服务器是否可用。即使你正在执行某些跨域脚本,修改你现有的函数以适应它可能是最好的选择。


1
虽然这是检查CORS请求的好方法,但它在IE7上会出现问题,而生成的锚点不会返回有关链接的任何信息,因此例如在IE7中评估链接<a href='main.html'></a>,它将返回a.href => "main.html" a.hostname => ""等。我仍在努力弄清楚如何在IE7中处理这个问题,但找不到任何好的解决方案,因为它永远不会创建完整的href。 - zanona
你会添加什么样的document.domain检查呢?只是用document.domain替换loc.hostname是否正确? - B T
@BT a.hostname 仍将是 "foo.example.com",而 document.domain 只是 "example.com"。这将需要进行特殊情况检查,即如果您知道正在重写两个 document.domain 为 "example.com",则主机名检查将类似于 a.hostname == loc.hostname || a.hostname == "foo.example.com" - Dagg Nabbit
我遇到了这样一个情况,即 a.port != loc.port,因为第一个端口显示为80,而第二个端口为空白。 - srmark

8

有趣的问题!我搜索了一下,除了你发布的内容之外,我没有找到其他任何东西,但当我在测试代码时,我偶然发现了这个。如果你只想简单地测试URL而不进行请求,那么我会像你现在这样做。如果你不介意发出请求来测试,你可以尝试这个:

向任何你想要的URL发起一个简单的ajax请求:

var ajaxRequest = $.ajax({
  url: 'http://www.google.com',
  async: false
});

该函数返回一个jqXHR对象,您可以对其进行检查:

ajaxRequest.isRejected(); // or...
ajaxRequest.isResolved();

现在,唯一的问题是isRejected()会对页面未能加载的所有情况(如404 Not Found等)都返回true,但您可以使用以下代码来检查状态码:
ajaxRequest.status;

看起来当你试图打破同源策略时,上述行将返回0,但在其他情况下会返回适当的错误代码(例如404)。

因此,为了总结一下,也许你可以尝试做以下事情:

function testSameOrigin(testUrl) {

  var ajaxRequest = $.ajax({
    url: testUrl,
    async: false
  });

  return ajaxRequest.isRejected() && ajaxRequest.status === 0;
}

这并不是一个明确的答案,但我希望它能帮助你找到你想要的东西!


是的,那是一个有趣的方法。在我的原始情况中,我想通过尝试“验证”URL来摆脱在拒绝的ajax调用时出现的令人讨厌的控制台错误。我最近还发现,在本地文件(file:协议)上执行ajax请求也会每次失败。因此,这是另一个需要检查的情况... - David Hellsing
你可以将 isLocal 标识设置为 true,以便使用 AJAX 处理本地文件。http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings - Steve

3

也可以尝试这个解决方案。

function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

function sameOrigin(url) {
  // test that a given url is a same-origin URL
  // url could be relative or scheme relative or absolute
  var host = window.document.location.host; // host + port
  var protocol = window.document.location.protocol;
  var srOrigin = '//' + host;
  var origin = protocol + srOrigin;
  // Allow absolute or scheme relative URLs to same origin
  return (url === origin || url.slice(0, origin.length + 1) === origin + '/') ||
    (url === srOrigin || url.slice(0, srOrigin.length + 1) === srOrigin + '/') ||
    // or any other URL that isn't scheme relative or absolute i.e relative.
    !(/^(\/\/|http:|https:).*/.test(url));
}

// if you want to check before you make a call
if (!csrfSafeMethod(data.type) && sameOrigin(data.url)) {
  // ...
}

// or if you want to set csrf token
$.ajax({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
      xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken"));
    }
  }
});

0

在Dagg Nabbit的回答基础上,这似乎更完整一些:

function sameOrigin(url) {
    var loc = window.location, a = document.createElement('a')
    a.href = url

    return a.hostname === loc.hostname &&
           a.port === loc.port &&
           a.protocol === loc.protocol &&
           loc.protocol !== 'file:'
}

我能想到的注意事项:


0
另一种执行跨域脚本的方式是使用 JSON-P。您还可以阅读这篇 文章。否则,同源策略将不允许跨域脚本。

我不确定为什么所有的答案都是关于如何制作或不制作跨浏览器的ajax请求,这完全不是我所问的... 我需要更清楚吗? - David Hellsing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接