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

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

net::ERR_INSECURE_RESPONSE
net::ERR_CONNECTION_REFUSED

responseText всегда ", а statusCode всегда" 0" для обоих случаев.

Мой вопрос в том, как я могу определить, не вызвал ли вызов jQuery ajax из-за ERR_INSECURE_RESPONSE или из-за ERR_CONNECTION_REFUSED?

Как только сертификат принят, все работает нормально, но я хочу знать, выключен ли сервер localhost или его запуск, но сертификат еще не принят.

$.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.
    }
});

enter image description here

Даже выполняя вручную запрос, я получаю тот же результат:

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();

Ответ 1

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

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

Описанные ниже шаги описывают, какие пользовательские агенты должны выполнять для простого запроса на перекрестный поиск:

Примените шаги make request и соблюдайте правила запроса ниже при оформлении запроса.

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

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

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

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

2) Uncaught 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", не имеет подсказки, чтобы отличать одну ошибку от другой.


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

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

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

Чтобы запустить его, просто выполните node applicationServer.js и node targetServer.js в терминале (пример ubuntu).

Ответ 2

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

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


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


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

Общий консенсус моего исследования заключается в том, что SSL-сертификаты обрабатываются браузером, поэтому до тех пор, пока пользовательский сертификат не будет принят пользователем, браузер блокирует все запросы, в том числе для кода состояния. Браузер мог (если был закодирован) отправить обратно свой код состояния для небезопасного ответа, но это ничего не помогает, и даже тогда у вас будут проблемы с совместимостью браузера (хром/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();

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

Ответ 3

Ответ на

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

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

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

Итак, мне приходят два подхода:

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

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


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

Ответ 5

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

Ответ 6

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

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

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

Вот код, где я это делаю: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transaction_details.js#L88