Określ, czy wywołanie ajax nie powiodło się z powodu niezabezpieczonej odpowiedzi lub odrzucenia połączenia

115

Przeprowadziłem wiele badań i nie mogłem znaleźć sposobu, aby sobie z tym poradzić. Próbuję wykonać wywołanie jQuery ajax z serwera https do lokalnego serwera https z uruchomionym pomostem z niestandardowym certyfikatem z podpisem własnym. Mój problem polega na tym, że nie mogę określić, czy odpowiedzią jest odmowa połączenia, czy odpowiedź niepewna (z powodu braku akceptacji certyfikatu). Czy istnieje sposób na określenie różnicy między obydwoma scenariuszami? Te responseText, i statusCodesą zawsze takie same w obu przypadkach, mimo że w konsoli chrome widać różnicę:

net::ERR_INSECURE_RESPONSE
net::ERR_CONNECTION_REFUSED

responseTextjest zawsze „” i statusCodezawsze wynosi „0” w obu przypadkach.

Moje pytanie brzmi, jak mogę określić, czy wywołanie jQuery ajax nie powiodło się z powodu ERR_INSECURE_RESPONSElub z powodu ERR_CONNECTION_REFUSED?

Po zaakceptowaniu certyfikatu wszystko działa poprawnie, ale chcę wiedzieć, czy serwer localhost jest wyłączony, czy działa i działa, ale certyfikat nie został jeszcze zaakceptowany.

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

wprowadź opis obrazu tutaj

Nawet wykonując żądanie ręcznie, otrzymuję ten sam wynik:

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();
taxicala
źródło
6
wyślę swój kod, ale nie jest to błąd kodu javascript. Przeczytaj uważnie moje pytanie.
taksówki
1
Czy pozostałe dwa argumenty wywołania zwrotnego błędu dają dodatkowe informacje? function (xhr, status, msg) {...Wątpię, że tak będzie, ale warto spróbować.
Kevin B
2
Nie. Z serwera na inny serwer. Serwer jetty (działający w locahost) poprawnie ustawił nagłówki CORS, ponieważ po zaakceptowaniu certyfikatu wszystko działa zgodnie z oczekiwaniami. Chcę ustalić, czy certyfikat musi zostać zaakceptowany, czy pomost nie działa.
taksówki
5
Jest całkowicie możliwe, że brak informacji z przeglądarki jest całkowicie zamierzony. Po prostu „błąd” dostarczyłby pewnej ilości informacji proponowanemu hakerowi, np. „W tym systemie coś nasłuchuje na porcie itp.”
Katana314,
1
to nie jest coś, czego chcę, to jest coś, czego potrzebuję ze względu na projekt i naturę tego, co rozwijam. po stronie serwera nie wchodzi w grę.
taksówki

Odpowiedzi:

67

Nie ma sposobu, aby odróżnić go od najnowszych przeglądarek internetowych.

Specyfikacja W3C:

Poniższe kroki opisują, co muszą zrobić klienty użytkownika w przypadku prostego żądania z różnych źródeł :

Zastosuj kroki składania wniosku i przestrzegaj poniższych zasad podczas składania wniosku.

Jeśli flaga ręcznego przekierowania nie jest ustawiona, a odpowiedź ma kod stanu HTTP 301, 302, 303, 307 lub 308 Zastosuj kroki przekierowania.

Jeśli użytkownik końcowy anuluje żądanie, zastosuj kroki przerwania.

Jeśli wystąpił błąd sieciowy W przypadku błędów DNS, niepowodzenia negocjacji TLS lub innego rodzaju błędów sieciowych, zastosuj kroki błędu sieciowego . Nie żądaj żadnego rodzaju interakcji z użytkownikiem końcowym.

Uwaga: nie obejmuje to odpowiedzi HTTP wskazujących na jakiś rodzaj błędu, na przykład kod stanu HTTP 410.

W przeciwnym razie Sprawdź udostępnianie zasobów. Jeśli zwróci błąd, zastosuj kroki dotyczące błędu sieci. W przeciwnym razie, jeśli zwróci pass, zakończ ten algorytm i ustaw stan żądania między źródłami na sukces. W rzeczywistości nie zamykaj żądania.

Jak możesz przeczytać, błędy sieciowe nie obejmują odpowiedzi HTTP, które zawierają błędy, dlatego zawsze otrzymasz 0 jako kod statusu i "" jako błąd.

Źródło


Uwaga : Poniższe przykłady zostały wykonane przy użyciu przeglądarki Google Chrome w wersji 43.0.2357.130 i w środowisku, które stworzyłem, aby emulować OP one. Kod do konfiguracji znajduje się na dole odpowiedzi.


Myślałem, że podejście do obejścia tego byłoby wykonanie dodatkowego żądania przez HTTP zamiast HTTPS jako Ta odpowiedź, ale pamiętałem, że nie jest to możliwe, ponieważ nowsze wersje przeglądarek blokują mieszaną zawartość.

Oznacza to, że przeglądarka internetowa nie zezwoli na żądania za pośrednictwem protokołu HTTP, jeśli używasz protokołu HTTPS i odwrotnie.

Dzieje się tak od kilku lat, ale starsze wersje przeglądarek internetowych, takie jak Mozilla Firefox poniżej, wersje 23 pozwalają na to.

Dowody na to:

Wykonywanie żądania HTTP z HTTPS przy użyciu konsoli 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();

spowoduje następujący błąd:

Zawartość mieszana: strona pod adresem „ https: // localhost: 8000 / ” została załadowana przez HTTPS, ale zażądała niezabezpieczonego punktu końcowego XMLHttpRequest „ http: // localhost: 8001 / ”. To żądanie zostało zablokowane; zawartość musi być dostarczana przez HTTPS.

Ten sam błąd pojawi się w konsoli przeglądarki, jeśli spróbujesz to zrobić w inny sposób, jak dodanie elementu iframe.

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

Używanie połączenia Socket zostało również opublikowane jako odpowiedź , byłem prawie pewien, że wynik będzie taki sam / podobny, ale spróbuję.

Próba otwarcia połączenia przez gniazdo z przeglądarki internetowej przy użyciu protokołu HTTPS do punktu końcowego niezabezpieczonego gniazda zakończy się błędami zawartości mieszanej.

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

1) Zawartość mieszana: Strona pod adresem „ https: // localhost: 8000 / ” została załadowana przez HTTPS, ale próbowała połączyć się z niezabezpieczonym punktem końcowym WebSocket „ws: // localhost: 8001 /”. To żądanie zostało zablokowane; ten punkt końcowy musi być dostępny przez WSS.

2) Uncaught DOMException: nie udało się utworzyć „WebSocket”: niezabezpieczone połączenie WebSocket nie może zostać zainicjowane ze strony załadowanej przez HTTPS.

Następnie próbowałem również połączyć się z punktem końcowym wss, zobacz Jeśli mogę przeczytać informacje o błędach połączenia sieciowego:

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

Wykonanie powyższego fragmentu z wyłączonym serwerem skutkuje:

Połączenie przez WebSocket z „wss: // localhost: 8001 /” nie powiodło się: Błąd podczas ustanawiania połączenia: net :: ERR_CONNECTION_REFUSED

Wykonywanie powyższego fragmentu z włączonym serwerem

Połączenie WebSocket z „wss: // localhost: 8001 /” nie powiodło się: anulowano uzgadnianie otwierania protokołu WebSocket

Ale znowu, błąd, że "funkcja onerror" wyprowadzana do konsoli nie ma żadnej wskazówki, która pozwoliłaby odróżnić jeden błąd od drugiego.


Używanie proxy, jak sugeruje ta odpowiedź, może działać, ale tylko wtedy, gdy „docelowy” serwer ma dostęp publiczny.

W tym przypadku tak nie było, więc próba zaimplementowania serwera proxy w tym scenariuszu doprowadzi nas do tego samego problemu.

Kod do utworzenia serwera HTTPS Node.js :

Stworzyłem dwa serwery HTTPS Nodejs, które używają certyfikatów z podpisem własnym:

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

Aby to działało, musisz mieć zainstalowany Nodejs, musisz wygenerować oddzielne certyfikaty dla każdego serwera i odpowiednio przechowywać go w folderach certs i certs2.

Aby go uruchomić wystarczy wykonać node applicationServer.jsoraz node targetServer.jsw terminalu (np ubuntu).

ecarrizo
źródło
6
Jak dotąd jest to najpełniejsza odpowiedź. Pokazuje, że faktycznie wykonałeś prace badawcze i testy. Widzę, że wypróbowałeś każdy możliwy sposób, aby to wykonać i wszystkie scenariusze są naprawdę dobrze wyjaśnione. Podałeś również przykładowy kod, aby szybko skonfigurować środowisko testowe! Naprawdę dobra robota! Dzięki za wgląd!
taksówki
Doskonała odpowiedź! Chciałbym jednak zwrócić uwagę, że certyfikaty z podpisem własnym nadal będą powodować błędy w przeglądarce, jeśli pochodzą z oddzielnych serwerów. @ecarrizo
FabricioG
Jeśli mówimy o błędzie sieci, to prawdopodobnie jesteś w stanie zobaczyć błędy w konsoli, ręcznie. ale nie masz do niego dostępu z kodu w bezpiecznym środowisku.
ecarrizo
32

Od teraz: nie ma sposobu, aby odróżnić to wydarzenie między przeglądarkami. Ponieważ przeglądarki nie zapewniają dostępu programistom do zdarzeń. (Lipiec 2015)

Ta odpowiedź ma jedynie na celu przedstawienie pomysłów na potencjalne, choć niekompletne i niekompletne rozwiązanie.


Zastrzeżenie: ta odpowiedź jest niekompletna, ponieważ nie rozwiązuje całkowicie problemów OP (ze względu na zasady dotyczące różnych źródeł). Jednak sam pomysł ma pewne zalety , które są dalej rozwijane przez: @artur grzesiak tutaj , używając proxy i ajax.


Po wielu badaniach nie wydaje się, aby istniała żadna forma sprawdzania błędów pod kątem różnicy między odmową połączenia a niezabezpieczoną odpowiedzią, przynajmniej w zakresie, w jakim javascript zapewnia odpowiedź na różnicę między nimi.

Ogólny konsensus moich badań jest taki, że certyfikaty SSL są obsługiwane przez przeglądarkę, więc dopóki sam certyfikat z podpisem nie zostanie zaakceptowany przez użytkownika, przeglądarka blokuje wszystkie żądania, w tym te dotyczące kodu stanu. Przeglądarka mogłaby (jeśli została zakodowana) odesłać swój własny kod stanu dla niezabezpieczonej odpowiedzi, ale to tak naprawdę nic nie pomaga, a nawet wtedy miałbyś problemy z kompatybilnością przeglądarki (chrome / firefox / IE mają różne standardy. .. jeszcze raz)

Ponieważ Twoje pierwotne pytanie dotyczyło sprawdzenia stanu serwera między działaniem a posiadaniem nieakceptowanego certyfikatu, czy nie mógłbyś wysłać standardowego żądania 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();

To prawda, że ​​wymaga to dodatkowego żądania w celu zweryfikowania łączności z serwerem, ale powinno to wykonać proces eliminacji. Nadal jednak czuję się zepsuty i nie jestem wielkim fanem podwójnej prośby.

acupofjose
źródło
7
Przeczytałem całą odpowiedź i nie sądzę, żeby to zadziałało. Oba moje serwery używają HTTPS, co oznacza, że ​​w momencie, gdy odpalę żądanie do serwera HTTP, przeglądarka od razu je anuluje, ponieważ nie można wysyłać żądań ajax z serwera HTTPS do serwera HTTP
taksówka
11

Odpowiedź @ Schultzie jest dość bliska, ale wyraźnie http- ogólnie - nie będzie działać httpsw środowisku przeglądarki.

Możesz jednak skorzystać z serwera pośredniego (proxy), aby wysłać żądanie w Twoim imieniu. Serwer proxy powinien umożliwiać przekazywanie httpżądań ze httpsźródła lub ładowanie treści z źródeł z podpisem własnym .

Posiadanie własnego serwera z odpowiednim certyfikatem jest prawdopodobnie przesadą w twoim przypadku - ponieważ możesz użyć tego ustawienia zamiast maszyny z certyfikatem z podpisem własnym - ale istnieje wiele anonimowych otwartych usług proxy .

Tak więc przychodzą mi do głowy dwa podejścia:

  1. Żądanie ajax - w takim przypadku proxy musi użyć odpowiednich ustawień CORS
  2. użycie elementu iframe - ładujesz skrypt (prawdopodobnie opakowany w html) wewnątrz elementu iframe za pośrednictwem serwera proxy. Po załadowaniu skrypt wysyła wiadomość do swojego .parentWindow. Jeśli twoje okno otrzymało wiadomość, możesz być pewien, że serwer jest uruchomiony (a dokładniej działał ułamek sekundy wcześniej).

Jeśli interesuje Cię tylko lokalne środowisko, możesz spróbować uruchomić chrome z --disable-web-securityflagą.


Kolejna sugestia: czy próbowałeś załadować obraz programowo, aby dowiedzieć się, czy jest tam więcej informacji?

artur grzesiak
źródło
1

Sprawdź jQuery.ajaxError () Odniesienie z: jQuery AJAX Error Handling (HTTP Status Codes) Przechwytuje globalne błędy Ajax, które możesz obsłużyć na wiele sposobów przez HTTP lub HTTPS:

if (jqXHR.status == 501) {
//insecure response
} else if (jqXHR.status == 102) {
//connection refused
}
Varshaan
źródło
To jest to samo, co robienie Ajax w sposób, w jaki to robię, ale centralizowanie odpowiedzi na błędy w jednym miejscu.
taksówki
1
Chodzi o to, że certyfikat nie jest ładowany podczas wywołania Ajax, ponieważ wydaje się, że jest to problem. jakikolwiek sposób gl z szukaniem odpowiedzi :)
Varshaan
2
Nie, problem polega na tym, że chcę określić różnicę między koniecznością akceptacji certyfikatu a wiedzą, czy serwer jest wyłączony.
taxicala
1
Zarówno w przypadku odmowy połączenia, jak i gdy certyfikat nie jest zaufany, otrzymuję ten sam jqXHR.status = 0 (i jqXHR.readyState = 0), a nie qXHR.status == 102 lub 501, jak opisano w tej odpowiedzi.
Anre
1

Niestety, dzisiejszy interfejs API XHR przeglądarki nie podaje wyraźnego wskazania, kiedy przeglądarka odmawia połączenia z powodu „niezabezpieczonej odpowiedzi”, a także kiedy nie ufa certyfikatowi HTTP / SSL witryny.

Ale są sposoby na obejście tego problemu.

Jednym z rozwiązań, które wymyśliłem, aby określić, kiedy przeglądarka nie ufa certyfikatowi HTTP / SSL, polega na pierwszym wykryciu, czy wystąpił błąd XHR ( error()na przykład przy użyciu wywołania zwrotnego jQuery ), a następnie sprawdzenie, czy wywołanie XHR jest skierowane do 'https : // '', a następnie sprawdź, czy XHR readyStatema wartość 0, co oznacza, że ​​połączenie XHR nie zostało nawet otwarte (co się dzieje, gdy przeglądarka nie polubi certyfikatu).

Oto kod, w którym to robię: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transaction_details.js

maratbn
źródło
1

Nie sądzę, aby obecnie istniał sposób na wykrycie tych komunikatów o błędach, ale sztuczka, którą możesz zrobić, to użyć serwera takiego jak nginx przed serwerem aplikacji, więc jeśli serwer aplikacji nie działa, otrzymasz zły błąd bramy z nginx z 502kodem statusu, który można wykryć w JS. W przeciwnym razie, jeśli certyfikat jest nieważny, nadal będziesz otrzymywać ten sam błąd ogólny z statusCode = 0.

gafi
źródło