Automatische HTTPS-Verbindung / Umleitung mit node.js / express

180

Ich habe versucht, HTTPS mit einem node.js-Projekt einzurichten, an dem ich arbeite. Ich habe im Wesentlichen die Dokumentation zu node.js für dieses Beispiel befolgt :

// curl -k https://localhost:8000/
var https = require('https');
var fs = require('fs');

var options = {
  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
};

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

Nun, wenn ich es tue

curl -k https://localhost:8000/

Ich bekomme

hello world

wie erwartet. Aber wenn ich es mache

curl -k http://localhost:8000/

Ich bekomme

curl: (52) Empty reply from server

Rückblickend scheint dies offensichtlich, dass es auf diese Weise funktionieren würde, aber gleichzeitig werden Leute, die mein Projekt schließlich besuchen, nicht https : // yadayada eingeben, und ich möchte, dass der gesamte Datenverkehr von dem Moment an https ist, in dem sie eintreffen der Standort.

Wie kann ich den Knoten (und Express, da dies das Framework ist, das ich verwende) dazu bringen, den gesamten eingehenden Datenverkehr an https weiterzuleiten, unabhängig davon, ob er angegeben wurde oder nicht? Ich konnte keine Dokumentation finden, die sich damit befasst. Oder wird nur angenommen, dass in einer Produktionsumgebung ein Knoten etwas vor sich hat (z. B. Nginx), das diese Art der Umleitung handhabt?

Dies ist mein erster Ausflug in die Webentwicklung. Bitte verzeihen Sie meine Unwissenheit, wenn dies offensichtlich ist.

Jake
quelle
Habe dies hier kurz
bündig
3
Für jeden, der sich für Heroku einsetzt, helfen die Antworten in dieser Frage nicht weiter (Sie erhalten "zu viele Weiterleitungen"), aber diese Antwort von einer anderen Frage wird
Rico Kahler

Antworten:

178

Ryan, danke, dass du mich in die richtige Richtung gelenkt hast. Ich habe Ihre Antwort (2. Absatz) ein wenig mit etwas Code ausgearbeitet und es funktioniert. In diesem Szenario werden diese Codefragmente in meine Express-App eingefügt:

// set up plain http server
var http = express.createServer();

// set up a route to redirect http to https
http.get('*', function(req, res) {  
    res.redirect('https://' + req.headers.host + req.url);

    // Or, if you don't want to automatically detect the domain name from the request header, you can hard code it:
    // res.redirect('https://example.com' + req.url);
})

// have it listen on 8080
http.listen(8080);

Der https-Express-Server überwacht den Geldautomaten auf 3000. Ich habe diese iptables-Regeln eingerichtet, damit der Knoten nicht als root ausgeführt werden muss:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 3000

Alles in allem funktioniert das genau so, wie ich es wollte.

Jake
quelle
15
Wirklich wichtige Frage (bezüglich der Sicherheit). Kann ein Angreifer, bevor diese Weiterleitung tatsächlich erfolgt, einen Cookie (Sitzungs-ID) ausspähen und stehlen?
Costa
4
Wie würde ich dieses resultierende Symptom beheben? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
Bodine
16
Eigentlich scheint das besser zu sein , ... wickeln Sie einfach die Weiterleitung mitif(!req.secure){}
bodine
6
Ich möchte nur auf die Antwort auf @ Costas wichtige Frage zur Sicherheit hinweisen, die in einem anderen Beitrag gestellt wurde - stackoverflow.com/questions/8605720/…
ThisClark
2
Vielleicht möchten Sie eine if(req.protocol==='http')Aussage
Max
120

Wenn Sie herkömmlichen Ports folgen, da HTTP standardmäßig Port 80 und HTTPS standardmäßig Port 443 versucht, können Sie einfach zwei Server auf demselben Computer haben: Hier ist der Code:

var https = require('https');

var fs = require('fs');
var options = {
    key: fs.readFileSync('./key.pem'),
    cert: fs.readFileSync('./cert.pem')
};

https.createServer(options, function (req, res) {
    res.end('secure!');
}).listen(443);

// Redirect from http port 80 to https
var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
    res.end();
}).listen(80);

Test mit https:

$ curl https://127.0.0.1 -k
secure!

Mit http:

$ curl http://127.0.0.1 -i
HTTP/1.1 301 Moved Permanently
Location: https://127.0.0.1/
Date: Sun, 01 Jun 2014 06:15:16 GMT
Connection: keep-alive
Transfer-Encoding: chunked

Weitere Details: Nodejs HTTP und HTTPS über denselben Port

Basarat
quelle
4
res.writeHead(301, etc.)wird nur für GET-Aufrufe korrekt funktionieren, da 301der Client nicht angewiesen wird, dieselbe Methode zu verwenden. Wenn Sie die verwendete Methode (und alle anderen Parameter) beibehalten möchten, müssen Sie sie verwenden res.writeHead(307, etc.). Und wenn es immer noch nicht funktioniert, müssen Sie möglicherweise ein Proxy durchführen. Quelle: http://stackoverflow.com/a/17612942/1876359
Ocramot
111

Vielen Dank an diesen Typen: https://www.tonyerwin.com/2014/09/redirecting-http-to-https-with-nodejs.html

app.use (function (req, res, next) {
        if (req.secure) {
                // request was via https, so do no special handling
                next();
        } else {
                // request was via http, so redirect to https
                res.redirect('https://' + req.headers.host + req.url);
        }
});
Jeremias Binder
quelle
10
Das ist die beste Antwort!
Steffan
3
Einverstanden, sollte die erste Antwort sein.
1984,
12
Die folgende Zeile half dieser Lösung für mich zu arbeiten:app.enable('trust proxy');
arbel03
2
Wie oben angegeben ^^^^: 'Trust Proxy' ist erforderlich, wenn Sie sich hinter einem Proxy, einer Firewall oder einem Load Balancer befinden, der den Datenverkehr umleitet: z. B. AWS Load Balancing, das alle http- und https-Anforderungen an denselben Nicht-Root-Port sendet (z. B. 3000) auf dem Webserver Ihrer VPC.
Alanwaring
Tolle Antwort, ich habe mich nur gefragt, wie ich nach Nicht-WWW-URLs suchen und zu WWW umleiten soll
Richlewis
25

Mit Nginx können Sie den "x-forwarded-proto" -Header nutzen:

function ensureSec(req, res, next){
    if (req.headers["x-forwarded-proto"] === "https"){
       return next();
    }
    res.redirect("https://" + req.headers.host + req.url);  
}
Ccyrille
quelle
5
Ich fand req.headers ["x-forwarded-proto"] === "https") nicht zuverlässig, aber req.secure funktioniert!
Zugwalt
Ich fand das gleiche, das scheint sehr unzuverlässig.
Dacopenhagen
2
req.secure ist der richtige Weg, dies ist jedoch fehlerhaft, wenn Sie sich hinter einem Proxy befinden, da req.secure proto == "https" entspricht. Hinter einem Proxy-Express steht jedoch möglicherweise, dass Ihr Proto https ist, http
dacopenhagen
7
Sie müssen app.enable('trust proxy');: "Zeigt an, dass sich die App hinter einem nach vorne gerichteten Proxy befindet, und die X-Forwarded- * -Header verwenden, um die Verbindung und die IP-Adresse des Clients zu bestimmen." expressjs.com/en/4x/api.html#app.set
dskrvk
Behandeln Sie URLs nicht als Zeichenfolgen, sondern verwenden Sie stattdessen das URL-Modul.
arboreal84
12

Ab 0.4.12 gibt es keine wirklich saubere Möglichkeit, HTTP und HTTPS am selben Port mithilfe der HTTP / HTTPS-Server von Node abzuhören.

Einige Benutzer haben dieses Problem gelöst, indem der HTTPS-Server von Node (dies funktioniert auch mit Express.js) 443 (oder einen anderen Port) abhört und einen kleinen http-Server an 80 bindet und Benutzer an den sicheren Port umleitet.

Wenn Sie unbedingt in der Lage sein müssen, beide Protokolle an einem einzigen Port zu verarbeiten, müssen Sie nginx, lighttpd, apache oder einen anderen Webserver an diesen Port anschließen und als Reverse-Proxy für Node fungieren.

Ryan Olds
quelle
Danke Ryan. Mit "... einen kleinen http-Server an 80 binden und umleiten ..." meine ich vermutlich einen anderen Knoten- http-Server. Ich glaube, ich habe eine Vorstellung davon, wie das funktionieren könnte, aber können Sie auf einen Beispielcode verweisen? Wissen Sie auch, ob sich irgendwo in der Knoten-Roadmap eine "sauberere" Lösung für dieses Problem befindet?
Jake
Ihre Node.js-Anwendung kann mehrere http (s) -Server haben. Ich habe die Probleme mit Node.js ( github.com/joyent/node/issues ) und die Mailingliste ( groups.google.com/group/nodejs ) überprüft und keine gemeldeten Probleme gesehen, aber einige Beiträge zu diesem Problem auf der Mailingliste. Soweit ich das beurteilen kann, ist dies nicht in der Leitung. Ich würde empfehlen, es auf Github zu melden und zu sehen, welches Feedback Sie erhalten.
Ryan Olds
Wirklich wichtige Frage (bezüglich der Sicherheit). Kann ein "Angreifer", bevor diese Weiterleitung tatsächlich stattfindet, ein Cookie (Sitzungs-ID) ausspähen und stehlen?
Costa
Ja, ein 3xx-Statuscode und möglicherweise ein Standortheader werden an den Agenten zurückgesendet. Zu diesem Zeitpunkt fordert der Agent die im Standortheader angegebene URL an.
Ryan Olds
Es ist 2015, ist das immer noch der Fall?
TheEnvironmentalist
10

Sie können das Express-Force-https- Modul verwenden:

npm install --save express-force-https

var express = require('express');
var secure = require('express-force-https');

var app = express();
app.use(secure);
Mateus Ferreira
quelle
1
Verwenden Sie dies mit Vorsicht, da das Paket seit Jahren nicht mehr aktualisiert wurde. In AWS sind Probleme aufgetreten, bei denen die Codierung schlecht war.
Martavis P.
1
Schwester Paket: npmjs.com/package/express-to-https - aber ich habe keine Ahnung , ob es funktioniert / isbetter / etc
quetzalcoatl
Beachten Sie, dass, wenn Sie Middleware zum Bereitstellen statischer Dateien (einschließlich einseitiger Apps) verwenden, jede umgeleitete Middleware zuerst angehängt werden muss app. (siehe diese Antwort )
Matthew R.
9

Ich verwende die von Basarat vorgeschlagene Lösung, muss aber auch den Port überschreiben, da ich früher zwei verschiedene Ports für HTTP- und HTTPS-Protokolle hatte.

res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(http_port,https_port) + req.url });

Ich bevorzuge es auch, keinen Standardport zu verwenden, um NodeJS ohne Root-Rechte zu starten. Ich mag 8080 und 8443, weil ich aus vielen Jahren der Programmierung auf Tomcat stamme.

Meine komplette Datei wird

var fs = require('fs');
var http = require('http');
var http_port    =   process.env.PORT || 8080; 
var app = require('express')();

// HTTPS definitions
var https = require('https');
var https_port    =   process.env.PORT_HTTPS || 8443; 
var options = {
   key  : fs.readFileSync('server.key'),
   cert : fs.readFileSync('server.crt')
};

app.get('/', function (req, res) {
   res.send('Hello World!');
});

https.createServer(options, app).listen(https_port, function () {
   console.log('Magic happens on port ' + https_port); 
});

// Redirect from http port to https
http.createServer(function (req, res) {
    res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(http_port,https_port) + req.url });
    console.log("http request, will go to >> ");
    console.log("https://" + req.headers['host'].replace(http_port,https_port) + req.url );
    res.end();
}).listen(http_port);

Dann verwende ich iptable, um 80- und 443-Verkehr auf meinen HTTP- und HTTPS-Ports zu formulieren.

sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 8443
Lorenzo Eccher
quelle
Danke Lorenzo, das hat bei mir funktioniert. Anstatt Optionen zu verwenden, habe ich letsencrypt verwendet. Ich habe var options gelöscht. Ich habe die letsencrypt-Parameter (privateKey, Zertifikat, ca und Anmeldeinformationen) hinzugefügt und die Optionen in Anmeldeinformationen geändert: https.createServer (Anmeldeinformationen, App)
Nhon Ha
8

Diese Antwort muss aktualisiert werden, damit sie mit Express 4.0 funktioniert. So habe ich den separaten http-Server zum Laufen gebracht:

var express = require('express');
var http = require('http');
var https = require('https');

// Primary https app
var app = express()
var port = process.env.PORT || 3000;
app.set('env', 'development');
app.set('port', port);
var router = express.Router();
app.use('/', router);
// ... other routes here
var certOpts = {
    key: '/path/to/key.pem',
    cert: '/path/to/cert.pem'
};
var server = https.createServer(certOpts, app);
server.listen(port, function(){
    console.log('Express server listening to port '+port);
});


// Secondary http app
var httpApp = express();
var httpRouter = express.Router();
httpApp.use('*', httpRouter);
httpRouter.get('*', function(req, res){
    var host = req.get('Host');
    // replace the port in the host
    host = host.replace(/:\d+$/, ":"+app.get('port'));
    // determine the redirect destination
    var destination = ['https://', host, req.url].join('');
    return res.redirect(destination);
});
var httpServer = http.createServer(httpApp);
httpServer.listen(8080);
Klacks
quelle
1
Ich habe dies durch Ändern von httpApp.use ('*', httpRouter) zum Laufen gebracht. zu httpApp.use ('/', httpRouter); und verschieben Sie es in die Zeile, bevor Sie den HTTP-Server erstellen.
KungWaz
7

Wenn sich Ihre App hinter einem vertrauenswürdigen Proxy befindet (z. B. einem AWS ELB oder einem korrekt konfigurierten Nginx), sollte dieser Code funktionieren:

app.enable('trust proxy');
app.use(function(req, res, next) {
    if (req.secure){
        return next();
    }
    res.redirect("https://" + req.headers.host + req.url);
});

Anmerkungen:

  • Dies setzt voraus, dass Sie Ihre Site auf 80 und 443 hosten. Andernfalls müssen Sie den Port bei der Umleitung ändern
  • Dies setzt auch voraus, dass Sie das SSL auf dem Proxy beenden. Wenn Sie SSL von Ende zu Ende ausführen, verwenden Sie die Antwort von @basarat oben. End-to-End-SSL ist die bessere Lösung.
  • Mit app.enable ('Trust Proxy') kann Express den X-Forwarded-Proto-Header überprüfen
Jamie McCrindle
quelle
6

Ich finde, dass req.protocol funktioniert, wenn ich Express verwende (habe nicht ohne getestet, aber ich vermute, dass es funktioniert). unter Verwendung des aktuellen Knotens 0.10.22 mit Express 3.4.3

app.use(function(req,res,next) {
  if (!/https/.test(req.protocol)){
     res.redirect("https://" + req.headers.host + req.url);
  } else {
     return next();
  } 
});
Katalysator
quelle
5

Die meisten Antworten hier schlagen vor, den Header req.headers.host zu verwenden.

Der Host-Header wird von HTTP 1.1 benötigt, ist jedoch optional, da der Header möglicherweise nicht von einem HTTP-Client gesendet wird und Node / Express diese Anforderung akzeptiert.

Sie könnten fragen: Welcher HTTP-Client (z. B. Browser) kann eine Anfrage senden, bei der dieser Header fehlt? Das HTTP-Protokoll ist sehr trivial. Sie können eine HTTP-Anforderung in wenigen Codezeilen erstellen, um keinen Host-Header zu senden. Wenn Sie jedes Mal eine fehlerhafte Anforderung erhalten, wird eine Ausnahme ausgelöst. Je nachdem, wie Sie mit solchen Ausnahmen umgehen, kann dies Ihren Server herunterfahren.

Überprüfen Sie also immer alle Eingaben . Dies ist keine Paranoia. Ich habe Anfragen erhalten, denen der Host-Header in meinem Dienst fehlt.

Außerdem behandeln nie URLs als Strings . Verwenden Sie das Knoten-URL-Modul, um bestimmte Teile einer Zeichenfolge zu ändern. Das Behandeln von URLs als Zeichenfolgen kann auf viele verschiedene Arten ausgenutzt werden. Tu es nicht.

arboreal84
quelle
Ihre Antwort wäre perfekt, wenn Sie ein Beispiel gegeben hätten.
SerG
Klingt echt, aber einige Referenzen / Links wären toll.
Giwan
3
var express = require('express');
var app = express();

app.get('*',function (req, res) {
    res.redirect('https://<domain>' + req.url);
});

app.listen(80);

Dies ist, was wir verwenden und es funktioniert großartig!

Nick Kotenberg
quelle
1
Behandeln Sie URLs nicht als Zeichenfolgen, sondern verwenden Sie stattdessen das URL-Modul.
arboreal84
4
Nennen Sie Beispiele mit Verantwortungsbewusstsein. Stapelüberlauf ist das Telefonspiel.
arboreal84
1
Dies wird keine Endlosschleife verursachen?
Muhammad Umer
Nein, da es nur Port 80 abhört und an Port 443 sendet. Die einzige Möglichkeit, eine Endlosschleife zu erhalten, besteht darin, dass ein Listener auf 443 auf 80 umleitet, was nicht in meinem Code enthalten ist. Ich hoffe das ergibt Sinn. Vielen Dank!
Nick Kotenberg
3

Sie können das "net" -Modul verwenden, um auf HTTP und HTTPS am selben Port zu warten

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

var net=require('net');
var handle=net.createServer().listen(8000)

var options = {
  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
};

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

http.createServer(function(req,res){
  res.writeHead(200);
  res.end("hello world\n");
}).listen(handle)
luofei614
quelle
5
Wenn ich dies auf Knoten 0.8 ausführe, scheint nur der letzte Server zu antworten, der .listen aufruft. In diesem Fall funktioniert HTTP, jedoch nicht HTTPS. Wenn ich die Reihenfolge von .createServer umkehre, funktioniert HTTP, jedoch nicht HTTPS. :(
Joe
1
Dies funktioniert nicht wie beschrieben. Ich kann das Problem bestätigen, das Joe gesehen hat.
Stepan Mazurov
2

Das hat bei mir funktioniert:

app.use(function(req,res,next) {
    if(req.headers["x-forwarded-proto"] == "http") {
        res.redirect("https://[your url goes here]" + req.url, next);
    } else {
        return next();
    } 
});
Shummy1991
quelle
Dies mag richtig sein, aber Sie sollten näher erläutern, wie dies die Frage des Fragestellers beantwortet.
Joe C
0

Das hat bei mir funktioniert:

/* Headers */
require('./security/Headers/HeadersOptions').Headers(app);

/* Server */
const ssl = {
    key: fs.readFileSync('security/ssl/cert.key'),
    cert: fs.readFileSync('security/ssl/cert.pem')
};
//https server
https.createServer(ssl, app).listen(443, '192.168.1.2' && 443, '127.0.0.1');
//http server
app.listen(80, '192.168.1.2' && 80, '127.0.0.1');
app.use(function(req, res, next) {
    if(req.secure){
        next();
    }else{
        res.redirect('https://' + req.headers.host + req.url);
    }
});

Empfehlen Sie, die Header hinzuzufügen, bevor Sie zu https umleiten

Wenn Sie dies tun:

curl http://127.0.0.1 --include

Du erhältst:

HTTP/1.1 302 Found
//
Location: https://127.0.0.1/
Vary: Accept
Content-Type: text/plain; charset=utf-8
Content-Length: 40
Date: Thu, 04 Jul 2019 09:57:34 GMT
Connection: keep-alive

Found. Redirecting to https://127.0.0.1/

Ich benutze Express 4.17.1

χρηστος απατσιδης
quelle