Stellen Sie fest, ob der Ajax-Aufruf aufgrund einer unsicheren Antwort fehlgeschlagen ist oder die Verbindung abgelehnt wurde

114

Ich habe viel recherchiert und konnte keinen Weg finden, damit umzugehen. Ich versuche, einen jQuery-Ajax-Aufruf von einem https-Server an einen lokalen https-Server auszuführen, auf dem Jetty mit einem benutzerdefinierten selbstsignierten Zertifikat ausgeführt wird. Mein Problem ist, dass ich nicht feststellen kann, ob es sich bei der Antwort um eine verweigerte Verbindung oder um eine unsichere Antwort handelt (aufgrund der fehlenden Zertifikatsakzeptanz). Gibt es eine Möglichkeit, den Unterschied zwischen beiden Szenarien zu bestimmen? Die responseTextund statusCodesind in beiden Fällen immer gleich, obwohl ich in der Chromkonsole einen Unterschied sehe:

net::ERR_INSECURE_RESPONSE
net::ERR_CONNECTION_REFUSED

responseTextist immer "" und statusCodeist in beiden Fällen immer "0".

Meine Frage ist, wie kann ich feststellen, ob ein jQuery-Ajax-Aufruf aufgrund ERR_INSECURE_RESPONSEoder aufgrund von fehlgeschlagen ist ERR_CONNECTION_REFUSED?

Sobald das Zertifikat akzeptiert wurde, funktioniert alles einwandfrei, aber ich möchte wissen, ob der localhost-Server heruntergefahren oder betriebsbereit ist, aber das Zertifikat noch nicht akzeptiert wurde.

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

Geben Sie hier die Bildbeschreibung ein

Selbst wenn ich die Anfrage manuell ausführe, erhalte ich das gleiche Ergebnis:

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
quelle
6
Ich werde meinen Code veröffentlichen, aber es ist kein Javascript-Codefehler. Bitte lesen Sie meine Frage sorgfältig durch.
Taxicala
1
Geben die beiden anderen Fehlerrückrufargumente zusätzliche Einblicke? function (xhr, status, msg) {...Ich bezweifle, dass sie es tun, aber es lohnt sich, es zu versuchen.
Kevin B
2
Nein, von einem Server zu einem anderen Server. Der Anlegeserver (der in locahost ausgeführt wird) hat die CORS-Header ordnungsgemäß festgelegt, da nach der Annahme des Zertifikats alles wie erwartet funktioniert. Ich möchte feststellen, ob das Zertifikat akzeptiert werden muss oder der Steg ausgefallen ist.
Taxicala
5
Es ist durchaus möglich, dass der Mangel an Informationen aus dem Browser völlig beabsichtigt ist. Ein einfacher "Fehler" würde einem vorgeschlagenen Hacker eine gewisse Menge an Informationen liefern, z. "Dieses System hat etwas , das auf Port usw. lauscht."
Katana314
1
Es ist nicht etwas, das ich will, es ist etwas, das ich aufgrund des Designs und der Art dessen, was ich entwickle, brauche. Serverseite ist keine Option.
Taxicala

Antworten:

67

Es gibt keine Möglichkeit, es von den neuesten Webbrowsern zu unterscheiden.

W3C-Spezifikation:

In den folgenden Schritten wird beschrieben, was Benutzeragenten für eine einfache abgangsübergreifende Anforderung tun müssen :

Wenden Sie die Schritte zum Erstellen einer Anfrage an und beachten Sie die folgenden Anforderungsregeln, während Sie die Anfrage stellen.

Wenn das manuelle Umleitungsflag nicht gesetzt ist und die Antwort den HTTP-Statuscode 301, 302, 303, 307 oder 308 hat, wenden Sie die Umleitungsschritte an.

Wenn der Endbenutzer die Anforderung abbricht, wenden Sie die Abbruchschritte an.

Wenn ein Netzwerkfehler vorliegt Bei DNS-Fehlern, TLS-Aushandlungsfehlern oder anderen Arten von Netzwerkfehlern wenden Sie die Netzwerkfehlerschritte an . Fordern Sie keine Endbenutzerinteraktion an.

Hinweis: Dies schließt keine HTTP-Antworten ein, die auf einen Fehlertyp hinweisen, z. B. den HTTP-Statuscode 410.

Andernfalls führen Sie eine Überprüfung der gemeinsamen Nutzung von Ressourcen durch. Wenn es fehlschlägt, wenden Sie die Netzwerkfehlerschritte an. Andernfalls beenden Sie diesen Algorithmus, wenn er pass zurückgibt, und setzen Sie den Status der Cross-Origin-Anforderung auf Erfolg. Beenden Sie die Anfrage nicht.

Wie Sie lesen können, enthalten Netzwerkfehler keine HTTP-Antwort, die Fehler enthält. Aus diesem Grund erhalten Sie immer 0 als Statuscode und "" als Fehler.

Quelle


Hinweis : Die folgenden Beispiele wurden mit Google Chrome Version 43.0.2357.130 und in einer Umgebung erstellt, die ich zum Emulieren von OP One erstellt habe. Der Code für die Einrichtung befindet sich am Ende der Antwort.


Ich dachte, dass ein Ansatz, um dies zu umgehen, eine sekundäre Anfrage über HTTP anstelle von HTTPS als Diese Antwort stellen würde, aber ich habe mich daran erinnert, dass dies nicht möglich ist, da neuere Versionen von Browsern gemischte Inhalte blockieren.

Das bedeutet, dass der Webbrowser keine Anforderung über HTTP zulässt, wenn Sie HTTPS verwenden und umgekehrt.

Dies ist seit einigen Jahren so, aber ältere Webbrowser-Versionen wie Mozilla Firefox darunter, Version 23, erlauben dies.

Beweise dafür:

Wenn Sie eine HTTP-Anfrage über HTTPS stellen, verwenden Sie die Web Broser-Konsole

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

führt zu folgendem Fehler:

Gemischter Inhalt: Die Seite unter ' https: // localhost: 8000 / ' wurde über HTTPS geladen, forderte jedoch einen unsicheren XMLHttpRequest-Endpunkt ' http: // localhost: 8001 / ' an. Diese Anfrage wurde blockiert. Der Inhalt muss über HTTPS bereitgestellt werden.

Der gleiche Fehler wird in der Browserkonsole angezeigt, wenn Sie versuchen, dies auf andere Weise als durch Hinzufügen eines Iframes zu tun.

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

Die Verwendung der Socket-Verbindung wurde ebenfalls als Antwort veröffentlicht. Ich war mir ziemlich sicher, dass das Ergebnis gleich / ähnlich sein wird, aber ich habe es versucht.

Der Versuch, eine Socket-Verbindung vom Web Broswer mithilfe von HTTPS zu einem nicht sicheren Socket-Endpunkt zu öffnen, führt zu Fehlern mit gemischtem Inhalt.

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

1) Gemischter Inhalt: Die Seite unter ' https: // localhost: 8000 / ' wurde über HTTPS geladen, es wurde jedoch versucht, eine Verbindung zum unsicheren WebSocket-Endpunkt 'ws: // localhost: 8001 /' herzustellen. Diese Anfrage wurde blockiert. Dieser Endpunkt muss über WSS verfügbar sein.

2) Nicht erfasste DOME-Ausnahme: Fehler beim Erstellen von 'WebSocket': Eine unsichere WebSocket-Verbindung kann möglicherweise nicht von einer über HTTPS geladenen Seite initiiert werden.

Dann habe ich auch versucht, eine Verbindung zu einem WSS-Endpunkt herzustellen. Sehen Sie, ob ich Informationen zu Netzwerkverbindungsfehlern lesen kann:

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

Das Ausführen des obigen Snippets bei ausgeschaltetem Server führt zu:

WebSocket-Verbindung zu 'wss: // localhost: 8001 /' fehlgeschlagen: Fehler beim Verbindungsaufbau: net :: ERR_CONNECTION_REFUSED

Ausführen des obigen Snippets bei eingeschaltetem Server

WebSocket-Verbindung zu 'wss: // localhost: 8001 /' fehlgeschlagen: Der Handshake zum Öffnen von WebSocket wurde abgebrochen

Aber auch hier hat der Fehler, dass die "Fehlerfunktion" an die Konsole ausgegeben wird, keinen Tipp, um einen Fehler vom anderen zu unterscheiden.


Die Verwendung eines Proxys, wie in dieser Antwort vorgeschlagen, könnte funktionieren, jedoch nur, wenn der "Ziel" -Server öffentlichen Zugriff hat.

Dies war hier nicht der Fall. Der Versuch, einen Proxy in diesem Szenario zu implementieren, führt uns zu demselben Problem.

Code zum Erstellen des HTTPS-Servers von Node.js :

Ich habe zwei Nodejs HTTPS-Server erstellt, die selbstsignierte Zertifikate verwenden:

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

Damit es funktioniert, müssen Nodejs installiert sein. Für jeden Server müssen separate Zertifikate generiert und entsprechend in den Ordnern certs und certs2 gespeichert werden.

Um Run führen Sie es einfach node applicationServer.jsund node targetServer.jsin einem Terminal (ubuntu Beispiel).

ecarrizo
quelle
6
Dies ist die bisher vollständigste Antwort. Es zeigt, dass Sie tatsächlich einige Forschungsarbeiten und Tests durchgeführt haben. Ich sehe, Sie haben alle möglichen Möglichkeiten ausprobiert, um dies durchzuführen, und alle Szenarien sind wirklich gut erklärt. Sie haben auch Beispielcode bereitgestellt, um schnell eine Testumgebung einzurichten! Wirklich gute Arbeit! Danke für den Einblick!
Taxicala
Hervorragende Antwort! Ich möchte jedoch darauf hinweisen, dass die selbstsignierten Zertifikate immer noch Fehler in einem Browser verursachen, wenn sie von separaten Servern stammen. @ecarrizo
FabricioG
Wenn es sich um einen Netzwerkfehler handelt, können Sie die Fehler wahrscheinlich manuell in der Konsole anzeigen. In einer sicheren Umgebung können Sie jedoch nicht über den Code darauf zugreifen.
Ecarrizo
32

Ab sofort: Es gibt keine Möglichkeit, dieses Ereignis zwischen Browers zu unterscheiden. Da die Browser kein Ereignis bereitstellen, auf das Entwickler zugreifen können. (Juli 2015)

Diese Antwort versucht lediglich, Ideen für eine mögliche, albiet hacky und unvollständige Lösung zu liefern.


Haftungsausschluss: Diese Antwort ist unvollständig, da sie die Probleme von OP nicht vollständig löst (aufgrund von Cross-Origin-Richtlinien). Die Idee selbst hat jedoch einige Vorteile , die durch Folgendes erweitert werden: @artur grzesiak hier unter Verwendung eines Proxys und eines Ajax .


Nach einigen Recherchen scheint es keine Form der Fehlerprüfung für den Unterschied zwischen verweigerter Verbindung und einer unsicheren Antwort zu geben, zumindest was Javascript betrifft, das eine Antwort auf den Unterschied zwischen beiden liefert.

Der allgemeine Konsens meiner Forschung besteht darin, dass SSL-Zertifikate vom Browser verarbeitet werden. Bis ein selbstsigniertes Zertifikat vom Benutzer akzeptiert wird, sperrt der Browser alle Anforderungen, einschließlich der Anforderungen für einen Statuscode. Der Browser könnte (falls codiert) seinen eigenen Statuscode für eine unsichere Antwort zurücksenden, aber das hilft nichts, und selbst dann hätten Sie Probleme mit der Browserkompatibilität (Chrome / Firefox / IE mit unterschiedlichen Standards). .. Noch einmal)

Könnten Sie nicht eine Standard-HTTP-Anfrage wie diese stellen, da Ihre ursprüngliche Frage darin bestand, den Status Ihres Servers zwischen dem Betrieb und einem nicht akzeptierten Zertifikat zu überprüfen?

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

Zugegeben, dies erfordert eine zusätzliche Anforderung zum Überprüfen der Serverkonnektivität, sollte jedoch die Aufgabe durch den Eliminierungsprozess erledigen. Fühlt sich trotzdem hackig an und ich bin kein großer Fan der doppelten Anfrage.

Akupofjose
quelle
7
Ich habe die gesamte Antwort gelesen und glaube nicht, dass dies funktionieren wird. Auf beiden Servern wird HTTPS ausgeführt. Dies bedeutet, dass der Browser im Moment, in dem ich eine Anfrage an einen HTTP-Server
abfeuere, diese sofort abbricht
11

@ Schultzies Antwort ist ziemlich nah, aber httpim Allgemeinen funktioniert sie httpsin der Browser-Umgebung eindeutig nicht .

Sie können jedoch einen Zwischenserver (Proxy) verwenden, um die Anforderung in Ihrem Namen zu stellen. Der Proxy sollte entweder das Weiterleiten von httpAnforderungen vom httpsUrsprung oder das Laden von Inhalten von selbstsignierten Ursprüngen ermöglichen .

Ein eigener Server mit ordnungsgemäßem Zertifikat ist in Ihrem Fall wahrscheinlich ein Overkill - da Sie diese Einstellung anstelle des Computers mit selbstsigniertem Zertifikat verwenden könnten -, aber es gibt viele anonyme offene Proxy-Dienste .

Zwei Ansätze, die mir in den Sinn kommen, sind:

  1. Ajax-Anfrage - In diesem Fall muss der Proxy die entsprechenden CORS-Einstellungen verwenden
  2. Verwendung eines Iframes - Sie laden Ihr Skript (wahrscheinlich in HTML verpackt) über den Proxy in einen Iframe. Sobald das Skript geladen ist, sendet es eine Nachricht an sein .parentWindow. Wenn Ihr Fenster eine Nachricht erhalten hat, können Sie sicher sein, dass der Server ausgeführt wird (oder genauer gesagt einen Bruchteil einer Sekunde zuvor ausgeführt wurde).

Wenn Sie nur an Ihrer lokalen Umgebung interessiert sind, können Sie versuchen, Chrome mit --disable-web-securityFlag auszuführen .


Ein weiterer Vorschlag: Haben Sie versucht, ein Bild programmgesteuert zu laden, um herauszufinden, ob dort weitere Informationen vorhanden sind?

artur grzesiak
quelle
1

Check out jQuery.ajaxError () Referenz: jQuery AJAX- Fehlerbehandlung (HTTP-Statuscodes) Es werden globale Ajax-Fehler abgefangen , die Sie über HTTP oder HTTPS auf verschiedene Arten behandeln können:

if (jqXHR.status == 501) {
//insecure response
} else if (jqXHR.status == 102) {
//connection refused
}
Varshaan
quelle
Dies ist das Gleiche wie das Ajax so zu machen, wie ich es mache, aber die Fehlerantworten an einem Ort zu zentralisieren.
Taxicala
1
Der Punkt ist, dass das Zertifikat nicht geladen wird, wenn der Ajax-Aufruf erfolgt, da dies das Problem zu sein scheint. Auf jeden
Fall
2
Nein, das Problem ist, dass ich den Unterschied zwischen dem Akzeptieren des Zertifikats und dem Wissen, ob der Server heruntergefahren ist, ermitteln möchte.
Taxicala
1
Sowohl wenn die Verbindung verweigert wird als auch das Zertifikat nicht vertrauenswürdig ist, erhalte ich den gleichen jqXHR.status = 0 (und jqXHR.readyState = 0), nicht qXHR.status == 102 oder 501, wie in dieser Antwort beschrieben.
Anre
1

Leider gibt die heutige XHR-API des Browsers keinen expliziten Hinweis darauf, wann der Browser aufgrund einer "unsicheren Antwort" die Verbindung verweigert und auch wenn er dem HTTP / SSL-Zertifikat der Website nicht vertraut.

Es gibt jedoch Möglichkeiten, dieses Problem zu umgehen.

Eine Lösung, die ich gefunden habe, um festzustellen, wann der Browser dem HTTP / SSL-Zertifikat nicht vertraut, besteht darin, zuerst zu erkennen, ob ein XHR-Fehler aufgetreten ist ( error()z. B. mithilfe des jQuery- Rückrufs), und dann zu überprüfen, ob der XHR-Aufruf ein https ist : // 'URL, und überprüfen Sie dann, ob die XHR readyState0 ist. Dies bedeutet, dass die XHR-Verbindung noch nicht einmal geöffnet wurde (was passiert, wenn der Browser das Zertifikat nicht mag).

Hier ist der Code, in dem ich dies mache: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transaction_details.js

maratbn
quelle
1

Ich glaube nicht, dass es derzeit eine Möglichkeit gibt, diese Fehlermeldungen zu erkennen, aber ein Hack, den Sie ausführen können, besteht darin, einen Server wie nginx vor Ihrem Anwendungsserver zu verwenden, sodass Sie bei einem Ausfall des Anwendungsservers einen Fehler erhalten Gateway-Fehler von Nginx mit 502Statuscode, den Sie in JS erkennen können. Andernfalls wird bei einem ungültigen Zertifikat immer noch der gleiche generische Fehler angezeigt statusCode = 0.

Gafi
quelle