确定ajax调用是不是由于不安全的响应或连接被拒绝而失败

Posted

技术标签:

【中文标题】确定ajax调用是不是由于不安全的响应或连接被拒绝而失败【英文标题】:Determine if ajax call failed due to insecure response or connection refused确定ajax调用是否由于不安全的响应或连接被拒绝而失败 【发布时间】:2015-09-12 13:21:44 【问题描述】:

我一直在做大量的研究,但找不到解决这个问题的方法。我正在尝试使用自定义自签名证书执行从 https 服务器到运行码头的 locahost https 服务器的 jQuery ajax 调用。我的问题是我无法确定响应是连接被拒绝还是不安全响应(由于缺乏证书接受)。有没有办法确定这两种情况之间的区别? responseTextstatusCode 在这两种情况下总是相同的,即使在 chrome 控制台中我可以看到不同之处:

net::ERR_INSECURE_RESPONSE
net::ERR_CONNECTION_REFUSED

responseText 始终为“”,statusCode 在这两种情况下始终为“0”。

我的问题是,我如何确定 jQuery ajax 调用是由于 ERR_INSECURE_RESPONSE 还是由于 ERR_CONNECTION_REFUSED 而失败?

一旦证书被接受,一切正常,但我想知道本地主机服务器是否已关闭,或者它已启动并运行但证书尚未被接受。

$.ajax(
    type: 'GET',
    url: "https://localhost/custom/server/",
    dataType: "json",
    async: true,
    success: function (response) 
        //do something
    ,
    error: function (xhr, textStatus, errorThrown) 
        console.log(xhr, textStatus, errorThrown); //always the same for refused and insecure responses.
    
);

即使手动执行请求,我也会得到相同的结果:

var request = new XMLHttpRequest();
request.open('GET', "https://localhost/custom/server/", true);
request.onload = function () 
    console.log(request.responseText);
;
request.onerror = function () 
    console.log(request.responseText);
;
request.send();

【问题讨论】:

我会发布我的代码,但这不是 javascript 代码错误。请仔细阅读我的问题。 其他两个错误回调参数是否提供了任何额外的见解? function (xhr, status, msg) ...我怀疑他们会,但值得一试。 没有。从一台服务器到另一台服务器。码头服务器(在 locahost 中运行)已正确设置 CORS 标头,因为一旦我接受证书,一切都会按预期工作。我想确定是否必须接受证书或码头已关闭。 浏览器缺少信息完全有可能是故意的。一个简单的“错误”就会向提议的黑客提供一些信息,例如。 “这个系统有 something 监听端口等。” 这不是我想要的东西,它是我设计和开发的本质所需要的东西。服务器端不是一个选项。 【参考方案1】:

我认为目前没有办法检测这些错误消息,但是您可以做的一个技巧是在您的应用程序服务器前面使用像 nginx 这样的服务器,这样如果应用程序服务器关闭,您将从 nginx 收到一个错误的网关错误,带有502 状态代码,您可以在 JS 中检测到该错误。否则,如果证书无效,您仍然会收到与 statusCode = 0 相同的一般错误。

【讨论】:

【参考方案2】:

不幸的是,当前的浏览器 XHR API 没有明确指示浏览器何时因“不安全响应”而拒绝连接,以及何时不信任网站的 HTTP/SSL 证书。

但是有办法解决这个问题。

我想出的一个确定浏览器何时不信任 HTTP/SSL 证书的解决方案是首先检测是否发生了 XHR 错误(例如使用 jQuery error() 回调),然后检查 XHR call 是一个'https://' URL,然后检查 XHR readyState 是否为 0,这意味着 XHR 连接甚至还没有打开(当浏览器不喜欢证书时会发生这种情况)。

这是我执行此操作的代码: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transaction_details.js#L88

【讨论】:

ERR CONNECTION TIMEOUT 的就绪状态有什么不同吗?还是这两个错误看起来一样?【参考方案3】:

没有办法将它与最新的 Web 浏览器区分开来。

W3C 规范:

以下步骤描述了用户代理必须为简单的跨域请求执行的操作

应用发出请求的步骤并在发出请求时遵守以下请求规则。

如果手动重定向标志未设置且响应的 HTTP 状态代码为 301、302、303、307 或 308 应用重定向步骤。

如果最终用户取消请求 应用中止步骤。

如果出现网络错误 如果出现 DNS 错误、TLS 协商失败或其他类型的网络错误,请应用 network error steps。不要请求任何类型的最终用户交互。

注意:这不包括指示某种类型错误的 HTTP 响应,例如 HTTP 状态代码 410。

否则 执行资源共享检查。如果返回失败,请应用网络错误步骤。否则,如果返回通过,则终止该算法并将跨域请求状态设置为成功。不要实际终止请求。

如您所见,网络错误不包括包含错误的 HTTP 响应,这就是为什么您将始终获得 0 作为状态代码,而将 "" 作为错误。

Source


注意:以下示例是使用 Google Chrome 版本 43.0.2357.130 并针对我创建的模拟 OP 的环境进行的。设置它的代码在答案的底部。


我认为解决此问题的方法是通过 HTTP 而不是 HTTPS 作为 This answer 发出辅助请求,但我记得这是不可能的,因为较新版本的浏览器会阻止混合内容。

这意味着如果您使用 HTTPS,Web 浏览器将不允许通过 HTTP 发出请求,反之亦然。

这从几年前就开始了,但旧的 Web 浏览器版本(例如 Mozilla Firefox 23 以下版本)允许这样做。

相关证据:

从 HTTPS ussign Web Broser 控制台发出 HTTP 请求

var request = new XMLHttpRequest();
request.open('GET', "http://localhost:8001", true);
request.onload = function () 
    console.log(request.responseText);
;
request.onerror = function () 
    console.log(request.responseText);
;
request.send();

将导致以下错误:

混合内容:“https://localhost:8000/”处的页面通过 HTTPS 加载,但请求了不安全的 XMLHttpRequest 端点“http://localhost:8001/”。此请求已被阻止;内容必须通过 HTTPS 提供。

如果您尝试以添加 Iframe 的其他方式执行此操作,浏览器控制台中将出现相同的错误。

<iframe src="http://localhost:8001"></iframe>

使用 Socket 连接也是 Posted as an answer,我很确定结果会相同/相似,但我试了一下。

尝试使用 HTTPS 从 Web Broswer 打开到非安全套接字端点的套接字连接将导致混合内容错误。

new WebSocket("ws://localhost:8001", "protocolOne");

1) 混合内容:“https://localhost:8000/”处的页面通过 HTTPS 加载,但尝试连接到不安全的 WebSocket 端点“ws://localhost:8001/”。此请求已被阻止;此端点必须在 WSS 上可用。

2) 未捕获的 DOMException:无法构造“WebSocket”:可能无法从通过 HTTPS 加载的页面启动不安全的 WebSocket 连接。

然后我也尝试连接到 wss 端点,请参阅是否可以阅读有关网络连接错误的一些信息:

var exampleSocket = new WebSocket("wss://localhost:8001", "protocolOne");
exampleSocket.onerror = function(e) 
    console.log(e);

在关闭服务器的情况下执行上面的 sn -p 会导致:

到 'wss://localhost:8001/' 的 WebSocket 连接失败:连接建立错误:net::ERR_CONNECTION_REFUSED

在服务器开启的情况下执行上面的 sn-p

到“wss://localhost:8001/”的 WebSocket 连接失败:WebSocket 打开握手被取消

但同样,“onerror 函数”输出到控制台的错误没有任何提示来区分另一个错误。


使用代理作为this answer suggest 可以工作,但前提是“目标”服务器具有公共访问权限。

这里不是这种情况,因此在这种情况下尝试实现代理会导致我们遇到同样的问题。

创建 Node.js HTTPS 服务器的代码

我创建了两个使用自签名证书的 Nodejs HTTPS 服务器:

targetServer.js:

var https = require('https');
var fs = require('fs');

var options = 
    key: fs.readFileSync('./certs2/key.pem'),
    cert: fs.readFileSync('./certs2/key-cert.pem')
;

https.createServer(options, function (req, res) 
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    res.writeHead(200);
    res.end("hello world\n");
).listen(8001);

applicationServer.js:

var https = require('https');
var fs = require('fs');

var options = 
    key: fs.readFileSync('./certs/key.pem'),
    cert: fs.readFileSync('./certs/key-cert.pem')
;

https.createServer(options, function (req, res) 
    res.writeHead(200);
    res.end("hello world\n");
).listen(8000);

要使其工作,您需要安装 Nodejs,需要为每个服务器生成单独的证书并将其存储在 certs 和 certs2 文件夹中。

要运行它,只需在终端中执行 node applicationServer.jsnode targetServer.js(ubuntu 示例)。

【讨论】:

这是迄今为止最完整的答案。它表明您实际上已经完成了一些研究工作和测试。我看到您已经尝试了所有可能的方法来执行此操作,并且所有场景都得到了很好的解释。您还提供了示例代码来快速设置测试环境!真是好作品!感谢您的洞察力! 优秀的答案!但是我想指出,如果自签名证书来自不同的服务器,它们仍然会从浏览器中产生错误。 @ecarrizo 如果我们谈论的是网络错误,可能您可以在控制台中手动查看错误。但您无法在安全环境中通过代码访问它。【参考方案4】:

截至目前:无法区分浏览器之间的此事件。由于浏览器不提供供开发人员访问的事件。(2015 年 7 月)

此答案仅旨在为潜在的、虽然老套且不完整的解决方案提供想法。


免责声明:此答案不完整,因为它没有完全解决 OP 的问题(由于跨域政策)。然而,这个想法本身确实有一些优点,通过以下方式进一步扩展:@artur grzesiak here,使用代理和 ajax。


在我自己进行了相当多的研究之后,似乎没有任何形式的错误检查来检查连接被拒绝和不安全响应之间的差异,至少就 javascript 为两者之间的差异提供响应而言。

我的研究普遍认为 SSL 证书由浏览器处理,因此在用户接受自签名证书之前,浏览器会锁定所有请求,包括状态码请求。浏览器可以(如果编码为)发回它自己的状态代码以获得不安全的响应,但这并没有真正帮助任何事情,即使那样,您也会遇到浏览器兼容性问题(chrome/firefox/IE 具有不同的标准。 ..再一次)

由于您最初的问题是检查您的服务器在启动与拥有未接受的证书之间的状态,您不能像这样发出标准的 HTTP 请求吗?

isUp = false;
isAccepted = false;

var isUpRequest = new XMLHttpRequest();
isUpRequest.open('GET', "http://localhost/custom/server/", true); //note non-ssl port
isUpRequest.onload = function() 
    isUp = true;
    var isAcceptedRequest = new XMLHttpRequest();
    isAcceptedRequest.open('GET', "https://localhost/custom/server/", true); //note ssl port
    isAcceptedRequest.onload = function() 
        console.log("Server is up and certificate accepted");
        isAccepted = true;
    
    isAcceptedRequest.onerror = function() 
        console.log("Server is up and certificate is not accepted");
    
    isAcceptedRequest.send();
;
isUpRequest.onerror = function() 
    console.log("Server is down");
;
isUpRequest.send();

当然,这确实需要额外的请求来验证服务器连接性,但它应该通过消除过程完成工作。尽管如此,我仍然觉得很老套,而且我不是双重请求的忠实粉丝。

【讨论】:

我已经阅读了整个答案,但我并不认为这会奏效。我的两台服务器都在运行 HTTPS,这意味着在我向 HTTP 服务器发出请求时,浏览器会立即取消它,因为您无法从 HTTPS 服务器向 HTTP 服务器发出 ajax 请求【参考方案5】:

查看jQuery.ajaxError() 参考自:jQuery AJAX Error Handling (HTTP Status Codes) 它捕获全局 Ajax 错误,您可以通过 HTTP 或 HTTPS 以多种方式处理这些错误:

if (jqXHR.status == 501) 
//insecure response
 else if (jqXHR.status == 102) 
//connection refused

【讨论】:

这和我做 ajax 的方式一样,但是将错误响应集中在一个地方。 关键是在进行 ajax 调用时没有加载证书,因为这似乎是问题所在。寻找答案的任何方法:) 不,问题是我想确定必须接受证书和知道服务器是否关闭之间的区别。 当连接被拒绝并且证书不受信任时,我得到相同的 jqXHR.status = 0(和 jqXHR.readyState = 0),而不是 qXHR.status == 102 或 501,如中所述这个答案。【参考方案6】:

@Schultzie 的答案非常接近,但显然 http - 通常 - 在浏览器环境中无法从 https 工作。

您可以做的是使用中间服务器(代理)代表您发出请求。代理应该允许转发来自https 来源的http 请求或从自签名来源 加载内容。

在您的情况下,拥有自己的带有适当证书的服务器可能有点过头了——因为您可以使用此设置而不是带有自签名证书的机器——但是有很多匿名开放代理服务 在那里。

所以我想到了两种方法:

    ajax 请求 -- 在这种情况下,代理必须使用适当的 CORS 设置 使用 iframe -- 您通过代理在 iframe 中加载脚本(可能包含在 html 中)。加载脚本后,它会向其.parentWindow 发送一条消息。如果您的窗口收到一条消息,您可以确定服务器正在运行(或者更准确地说,在几分之一秒之前运行)。

如果您只对本地环境感兴趣,可以尝试使用 --disable-web-security 标志运行 chrome。


另一个建议:您是否尝试以编程方式加载图像以查看那里是否存在更多信息?

【讨论】:

以上是关于确定ajax调用是不是由于不安全的响应或连接被拒绝而失败的主要内容,如果未能解决你的问题,请参考以下文章

Phpmyadmin 访问被拒绝

java中如何判断发送的请求是不是得到响应

AJAX 跨域方法

网络设备应急响应指南

连接SQL SERVER数据库提示不存在或访问被拒绝

获取拒绝访问错误