Определите, не удалось ли вызов ajax из-за небезопасного ответа или отказа в соединении


115

Я провел много исследований и не мог найти способ справиться с этим. Я пытаюсь выполнить вызов jQuery ajax с https-сервера на https-сервер locahost, на котором работает причал, с настраиваемым самоподписанным сертификатом. Моя проблема в том, что я не могу определить, является ли ответ отказом в соединении или небезопасным ответом (из-за отсутствия принятия сертификата). Есть ли способ определить разницу между обоими сценариями? В обоих случаях responseText, и statusCodeвсегда одинаковы, хотя в консоли 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();

6
Я отправлю свой код, но это не ошибка кода javascript. Пожалуйста, прочтите внимательно мой вопрос.
Taxicala

1
Дают ли другие два аргумента обратного вызова ошибки дополнительную информацию? function (xhr, status, msg) {...Я сомневаюсь, что они это сделают, но попробовать стоит.
Kevin B

2
Нет. С одного сервера на другой. Сервер причала (работающий в locahost) правильно установил заголовки CORS, потому что, как только я принимаю сертификат, все работает, как ожидалось. Я хочу определить, нужно ли принимать сертификат или пристань не работает.
Taxicala

5
Вполне возможно, что отсутствие информации в браузере полностью преднамеренно. Простая «ошибка» предоставит некоторый объем информации предполагаемому хакеру, например. "В этой системе что- то слушает порт и т. Д."
Katana314

1
это не то, что я хочу, это то, что мне нужно по дизайну и характеру того, что я разрабатываю. серверная часть не вариант.
taxicala

Ответы:


67

Невозможно отличить его от новейших веб-браузеров.

Спецификация W3C:

Следующие шаги описывают, что должны делать пользовательские агенты для простого запроса между источниками :

Примените шаги создания запроса и соблюдайте приведенные ниже правила при отправке запроса.

Если флаг ручного перенаправления не установлен и ответ имеет код состояния HTTP 301, 302, 303, 307 или 308 Примените шаги перенаправления.

Если конечный пользователь отменяет запрос Примените шаги отмены .

Если есть сетевая ошибка. В случае ошибок DNS, сбоя согласования TLS или других сетевых ошибок, примените шаги сетевой ошибки . Не запрашивайте никакого взаимодействия с конечным пользователем.

Примечание. Сюда не входят ответы HTTP, указывающие на какой-либо тип ошибки, например код состояния HTTP 410.

В противном случае выполните проверку совместного использования ресурсов. Если он возвращает ошибку, примените действия по устранению сетевой ошибки. В противном случае, если он возвращает pass, завершите этот алгоритм и установите статус запроса между источниками как успех. Фактически не прекращайте запрос.

Как вы можете прочитать, сетевые ошибки не включают HTTP-ответ, содержащий ошибки, поэтому вы всегда будете получать 0 в качестве кода состояния и "" в качестве ошибки.

Источник


Примечание . Следующие примеры были сделаны с использованием Google Chrome версии 43.0.2357.130 и в среде, которую я создал для имитации OP one. Код для настройки находится внизу ответа.


Я думал, что подход Чтобы обойти это было бы сделать вторичный запрос через HTTP вместо HTTPS, как этот ответ но я вспомнил, что это невозможно из-за того, что более новые версии браузеров блокируют смешанный контент.

Это означает, что веб-браузер не будет разрешать запросы через HTTP, если вы используете HTTPS, и наоборот.

Так было несколько лет назад, но более старые версии веб-браузеров, такие как Mozilla Firefox ниже 23 версии, допускают это.

Свидетельства об этом:

Выполнение HTTP-запроса от HTTPS с использованием консоли Web Broser

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-соединения также было отправлено в качестве ответа , я был уверен, что результат будет таким же / похожим, но я попробовал.

Попытка открыть сокет-соединение от Web Broswer с использованием HTTPS к незащищенной конечной точке сокета закончится ошибками смешанного содержимого.

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

1) Смешанный контент: страница по адресу https: // localhost: 8000 / была загружена через HTTPS, но была предпринята попытка подключиться к небезопасной конечной точке WebSocket ws: // localhost: 8001 /. Этот запрос был заблокирован; эта конечная точка должна быть доступна через WSS.

2) Неперехваченное исключение DOMException: не удалось создать «WebSocket»: небезопасное соединение WebSocket не может быть инициировано со страницы, загруженной через HTTPS.

Затем я попытался подключиться к конечной точке wss, посмотрите, могу ли я прочитать некоторую информацию об ошибках сетевого подключения:

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

Выполнение приведенного выше фрагмента с выключенным сервером приводит к:

Соединение WebSocket с 'wss: // localhost: 8001 /' не удалось: ошибка при установлении соединения: net :: ERR_CONNECTION_REFUSED

Выполнение приведенного выше фрагмента с включенным сервером

Соединение WebSocket с 'wss: // localhost: 8001 /' не удалось: рукопожатие открытия WebSocket было отменено

Но опять же, ошибка, выводимая на консоль "функцией onerror", не имеет подсказки, чтобы отличить одну ошибку от другой.


Использование прокси, как предлагает этот ответ, может работать, но только в том случае, если «целевой» сервер имеет открытый доступ.

Здесь этого не было, поэтому попытка реализовать прокси в этом сценарии приведет Нас к той же проблеме.

Код для создания HTTPS-сервера Node.js :

Я создал два HTTPS-сервера Nodejs, которые используют самозаверяющие сертификаты:

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.jsи node targetServer.jsв терминале (пример ubuntu).


6
На данный момент это наиболее полный ответ. Это показывает, что вы действительно проделали некоторую исследовательскую работу и тестирование. Я вижу, вы испробовали все возможные способы сделать это, и все сценарии действительно хорошо объяснены. Вы также предоставили пример кода для быстрой настройки тестовой среды! Действительно хорошая работа! Спасибо за понимание!
taxicala

Отличный ответ! Однако я хотел бы отметить, что самозаверяющие сертификаты по-прежнему будут вызывать ошибки в браузере, если они поступают с разных серверов. @ecarrizo
FabricioG

Если мы говорим об ошибке сети, возможно, вы можете увидеть ошибки в консоли, вручную. но у вас нет доступа к нему из кода в безопасной среде.
ecarrizo

32

На данный момент: нет возможности различать это событие между браузерами. Поскольку браузеры не предоставляют события для доступа разработчиков. (Июль 2015 г.)

Этот ответ просто пытается предоставить идеи для потенциального, хакерского и неполного решения.


Отказ от ответственности: этот ответ является неполным, поскольку он не полностью решает проблемы OP (из-за политики перекрестного происхождения). Однако сама идея имеет некоторые достоинства, которые далее расширяются: @artur grzesiak здесь , используя прокси и 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();

Конечно, для этого требуется дополнительный запрос на проверку подключения к серверу, но он должен выполнять свою работу путем исключения. Тем не менее, все еще кажется хакерским, и я не большой поклонник двойного запроса.


7
Я прочитал весь ответ и не думаю, что это сработает. Оба моих сервера работают по протоколу HTTPS, а это означает, что в тот момент, когда я отправляю запрос на HTTP-сервер, браузер сразу его отменит, потому что вы не можете делать запросы ajax с HTTPS-сервера на HTTP-сервер
тактикально

11

Ответ @ Schultzie довольно близок, но ясно http- в целом - не будет работать изhttps в среде браузера.

Однако вы можете использовать промежуточный сервер (прокси) для отправки запроса от вашего имени. Прокси-сервер должен позволять либо пересылать httpзапрос из httpsисточника, либо загружать контент из самоподписанных источников. .

Наличие собственного сервера с надлежащим сертификатом в вашем случае, вероятно, является излишним, поскольку вы можете использовать этот параметр вместо машины с самоподписанным сертификатом, но существует множество анонимных открытых прокси-сервисов. .

На ум приходят два подхода:

  1. запрос ajax - в этом случае прокси должен использовать соответствующие настройки CORS
  2. использование iframe - вы загружаете свой скрипт (вероятно, завернутый в html) внутри iframe через прокси. После загрузки скрипт отправляет сообщение на свой .parentWindow. Если ваше окно получило сообщение, вы можете быть уверены, что сервер запущен (точнее, работал на долю секунды раньше).

Если вас интересует только ваша локальная среда, вы можете попробовать запустить хром с --disable-web-securityфлагом.


Другое предложение: вы пытались программно загрузить изображение, чтобы узнать, есть ли там дополнительная информация?


1

Ознакомьтесь с 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 так, как я это делаю, но централизация ответов об ошибках в одном месте.
taxicala

1
Дело в том, что сертификат не загружается при вызове ajax, поскольку это кажется проблемой. любыми способами gl с поиском ответов :)
Varshaan

2
Нет, проблема в том, что я хочу определить разницу между принятием сертификата и знанием того, выключен ли сервер.
taxicala

1
И когда в соединении отказано, и сертификат не является доверенным, я получаю тот же jqXHR.status = 0 (и jqXHR.readyState = 0), а не qXHR.status == 102 или 501, как описано в этом ответе.
anre

1

К сожалению, современный браузер XHR API не предоставляет явных указаний на то, когда браузер отказывается подключаться из-за «небезопасного ответа», а также когда он не доверяет сертификату HTTP / SSL веб-сайта.

Но есть способы обойти эту проблему.

Одно из решений, которое я придумал, чтобы определить, когда браузер не доверяет сертификату HTTP / SSL, состоит в том, чтобы сначала определить, произошла ли ошибка XHR (например, с помощью error()обратного вызова jQuery ), а затем проверить, является ли вызов XHR на https : // 'URL, а затем проверьте, что XHRreadyState равен 0, что означает, что соединение XHR даже не было открыто (что происходит, когда браузеру не нравится сертификат).

Вот код, в котором я это делаю: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transactions_details.


1

Я не думаю, что в настоящее время есть способ обнаружить эти сообщения об ошибках, но вы можете взломать сервер, например nginx, перед вашим сервером приложений, так что если сервер приложений не работает, вы получите плохой ошибка шлюза от nginx с 502кодом состояния, который вы можете обнаружить в JS. В противном случае, если сертификат недействителен, вы все равно получите ту же общую ошибку с statusCode = 0.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.