Connessione / reindirizzamento HTTPS automatico con node.js / express


182

Ho provato a configurare HTTPS con un progetto node.js a cui sto lavorando. Ho essenzialmente seguito la documentazione node.js per questo esempio:

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

Ora, quando lo faccio

curl -k https://localhost:8000/

ottengo

hello world

come previsto. Ma se lo faccio

curl -k http://localhost:8000/

ottengo

curl: (52) Empty reply from server

In retrospettiva questo sembra ovvio che funzionerebbe in questo modo, ma allo stesso tempo, le persone che alla fine visitano il mio progetto non digiteranno https : // yadayada e voglio che tutto il traffico sia https dal momento in cui colpiscono il sito.

Come posso ottenere il nodo (ed Express come quello che sto usando) per trasferire tutto il traffico in entrata su https, indipendentemente dal fatto che sia stato specificato o no? Non sono stato in grado di trovare alcuna documentazione che abbia risolto questo problema. O è solo supposto che in un ambiente di produzione, il nodo abbia qualcosa che si trova di fronte (ad esempio nginx) che gestisce questo tipo di reindirizzamento?

Questa è la mia prima incursione nello sviluppo web, quindi per favore perdona la mia ignoranza se questo è qualcosa di ovvio.


Hanno risposto a questa succintamente qui: stackoverflow.com/a/23894573/1882064
arcseldon

3
per chiunque DEPLOYING to HEROKU, le risposte a questa domanda non ti aiuteranno (otterrai "troppi reindirizzamenti") ma questa risposta da un'altra domanda sarà
Rico Kahler

Risposte:


179

Ryan, grazie per avermi indicato nella giusta direzione. Ho arricchito un po 'la tua risposta (secondo paragrafo) con un po' di codice e funziona. In questo scenario questi frammenti di codice vengono inseriti nella mia app express:

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

Il server https express ascolta ATM su 3000. Ho impostato queste regole iptables in modo che il nodo non debba funzionare come root:

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

Tutti insieme, funziona esattamente come volevo.


15
Domanda davvero importante (per quanto riguarda la sicurezza). Prima che il reindirizzamento effettivamente si verifichi, è possibile che un utente malintenzionato possa annusare e rubare un cookie (ID sessione)?
Costa

4
Come riparerei questo sintomo risultante? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
corporeo

16
In realtà, questo sembra meglio , ... basta avvolgere il reindirizzamento conif(!req.secure){}
body

6
Voglio solo sottolineare la risposta all'importante domanda di @ Costa sulla sicurezza fornita in un altro post :
stackoverflow.com/questions/8605720/…

2
potrebbe voler concludere una if(req.protocol==='http')dichiarazione
Max

120

Se segui le porte convenzionali poiché HTTP prova la porta 80 per impostazione predefinita e HTTPS prova la porta 443 per impostazione predefinita, puoi semplicemente avere due server sullo stesso computer: Ecco il codice:

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 con https:

$ curl https://127.0.0.1 -k
secure!

Con 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

Maggiori dettagli: Nodejs HTTP e HTTPS sulla stessa porta


4
res.writeHead(301, etc.)funzionerà correttamente solo per le chiamate GET, poiché 301non sta dicendo al client di utilizzare lo stesso metodo. Se vuoi mantenere il metodo utilizzato (e tutti gli altri parametri) devi usare res.writeHead(307, etc.). E se ancora non funziona, potresti dover fare del proxy. Fonte: http://stackoverflow.com/a/17612942/1876359
ocramot

113

Grazie a questo ragazzo: 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);
        }
});

11
Questa è la risposta migliore!
Steffan,

3
D'accordo, dovrebbe essere la prima risposta.
1984,

12
La seguente riga ha aiutato questa soluzione a funzionare per me:app.enable('trust proxy');
arbel03

2
Come indicato sopra ^^^^: 'proxy di fiducia' è richiesto se si è dietro un qualsiasi tipo di proxy, firewall o bilanciamento del carico che reindirizza il traffico: ad es. Bilanciamento del carico AWS che invia tutte le richieste http & https alla stessa porta non root (ad es. 3000) sul server web del VPC.
alanwaring,

1
per AWS ELB utilizzare app.get('X-Forwarded-Proto') != 'http'al posto di req.secure aws.amazon.com/premiumsupport/knowledge-center/...
Shak

25

Con Nginx puoi sfruttare l'intestazione "x-forwarded-proto":

function ensureSec(req, res, next){
    if (req.headers["x-forwarded-proto"] === "https"){
       return next();
    }
    res.redirect("https://" + req.headers.host + req.url);  
}

5
Ho trovato req.headers ["x-forwarded-proto"] === "https") non affidabile, tuttavia req.secure funziona!
Zugwalt,

Ho trovato lo stesso, questo sembra molto inaffidabile.
dacopenhagen,

2
req.secure è il modo corretto, tuttavia questo è difettoso se si è dietro un proxy perché req.secure è equivalente a proto == "https", tuttavia dietro un proxy express si può dire che il tuo proto è https, http
dacopenhagen

7
È necessario app.enable('trust proxy');: "Indica che l'app si trova dietro un proxy frontale e di utilizzare le intestazioni X-Forwarded- * per determinare la connessione e l'indirizzo IP del client." expressjs.com/en/4x/api.html#app.set
dskrvk

Non trattare gli URL come stringhe, utilizza invece il modulo url.
arboreal84,

12

A partire dalla 0.4.12 non abbiamo un modo veramente pulito di ascoltare HTTP e HTTPS sulla stessa porta usando i server HTTP / HTTPS di Node.

Alcune persone hanno risolto questo problema facendo in modo che il server HTTPS di Node (funziona anche con Express.js) ascolta 443 (o qualche altra porta) e ha anche un piccolo server http che si collega a 80 e reindirizza gli utenti alla porta sicura.

Se devi assolutamente essere in grado di gestire entrambi i protocolli su una singola porta, devi inserire nginx, lighttpd, apache o qualche altro server web su quella porta e agire come proxy inverso per Node.


Grazie Ryan. Con "... fai associare un piccolo server http a 80 e reindirizza ..." Suppongo che intendi un altro server http nodo . Penso di avere un'idea di come potrebbe funzionare, ma puoi indicare un codice di esempio? Inoltre, sai se una soluzione "più pulita" a questo problema si trova ovunque nella roadmap del nodo?
Jake,

L'applicazione Node.js può avere più server http (s). Ho verificato i problemi di Node.js ( github.com/joyent/node/issues ) e la mailing list ( groups.google.com/group/nodejs ) e non ho visto alcun problema segnalato, ma ho visto un paio di post sul problema sulla mailing list. Per quanto posso dire questo non è nel tubo. Consiglierei di segnalarlo su Github e di vedere quale feedback ricevi.
Ryan Olds,

Domanda davvero importante (per quanto riguarda la sicurezza). Prima che il reindirizzamento avvenga effettivamente, è possibile che un "attaccante" annusi e rubi un cookie (ID sessione)?
Costa

Sì, un codice di stato 3xx e possibilmente un'intestazione di posizione vengono restituiti all'agente, a quel punto l'agente richiede l'URL specificato dall'intestazione di posizione.
Ryan Olds,

È il 2015, è ancora così?
TheEnvironmentalist

10

È possibile utilizzare il modulo express-force-https :

npm install --save express-force-https

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

var app = express();
app.use(secure);

1
Usare questo con cautela perché il pacchetto non è stato aggiornato da anni. Ho riscontrato problemi in AWS in cui la codifica era errata.
Martavis P.

1
pacchetto gemello : npmjs.com/package/express-to-https - ma non ho idea se funzioni / isbetter / etc
quetzalcoatl il

Se si utilizza il middleware per pubblicare file statici (comprese le app a pagina singola), è necessario prima allegare qualsiasi middleware di reindirizzamento app. (vedi questa risposta )
Matthew R.

9

Uso la soluzione proposta da Basarat ma ho anche bisogno di sovrascrivere la porta perché avevo 2 porte diverse per i protocolli HTTP e HTTPS.

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

Preferisco anche usare una porta non standard in modo da avviare nodejs senza i privilegi di root. Mi piacciono gli 8080 e gli 8443 perché vengo da molti anni di programmazione su Tomcat.

Il mio file completo diventa

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

Quindi utilizzo iptable per preconfigurare il traffico 80 e 443 sulle mie porte HTTP e HTTPS.

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

Grazie Lorenzo, ha funzionato per me. Invece di usare le opzioni, ho usato letsencrypt. Ho cancellato le opzioni var. Ho aggiunto i parametri letsencrypt (privateKey, certificate, ca e credentials) e ho cambiato le opzioni in credenziali: https.createServer (credenziali, app)
Nhon Ha

8

Questa risposta deve essere aggiornata per funzionare con Express 4.0. Ecco come ho fatto funzionare il server http separato:

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

1
Ho ottenuto che funzioni cambiando httpApp.use ('*', httpRouter); a httpApp.use ('/', httpRouter); e spostandolo sulla linea prima di creare il server hhtp.
KungWaz,

8

Se l'app si trova dietro un proxy attendibile (ad esempio un ELB AWS o un nginx correttamente configurato), questo codice dovrebbe funzionare:

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

Appunti:

  • Ciò presuppone che stai ospitando il tuo sito su 80 e 443, in caso contrario, dovrai cambiare la porta quando reindirizzi
  • Ciò presuppone anche che stai terminando SSL sul proxy. Se stai eseguendo SSL end to end usa la risposta di @basarat sopra. L'SSL end-to-end è la soluzione migliore.
  • app.enable ('proxy di fiducia') consente a express di controllare l'intestazione X-Forwarded-Proto

7

Trovo che req.protocol funziona quando sto usando express (non ho provato senza ma sospetto che funzioni). utilizzando il nodo corrente 0.10.22 con 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();
  } 
});

5

La maggior parte delle risposte qui suggerisce di usare l'intestazione req.headers.host.

L'intestazione Host è richiesta da HTTP 1.1, ma in realtà è facoltativa poiché l'intestazione potrebbe non essere effettivamente inviata da un client HTTP e node / express accetterà questa richiesta.

Potresti chiedere: quale client HTTP (es: browser) può inviare una richiesta mancante di quell'intestazione? Il protocollo HTTP è molto banale. È possibile creare una richiesta HTTP in poche righe di codice, per non inviare un'intestazione host e se ogni volta che si riceve una richiesta non valida si genera un'eccezione e, a seconda di come si gestiscono tali eccezioni, questo può far cadere il server.

Quindi convalida sempre tutti gli input . Questa non è paranoia, ho ricevuto richieste prive dell'intestazione host nel mio servizio.

Inoltre, non trattare mai gli URL come stringhe . Utilizzare il modulo URL del nodo per modificare parti specifiche di una stringa. Il trattamento degli URL come stringhe può essere sfruttato in molti modi. Non farlo


La tua risposta sarebbe perfetta se avessi dato un esempio.
SerG

Sembra legittimo ma alcuni riferimenti / collegamenti sarebbero fantastici.
Giwan,

3
var express = require('express');
var app = express();

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

app.listen(80);

Questo è ciò che usiamo e funziona alla grande!


1
Non trattare gli URL come stringhe, utilizza invece il modulo url.
arboreal84,

4
Fornisci esempi con un senso di responsabilità. Stack overflow è il gioco del telefono.
arboreal84,

1
questo non causerà un ciclo infinito?
Muhammad Umer,

No, perché ascolta solo sulla porta 80 e invia alla porta 443. L'unico modo in cui potrebbe accadere un ciclo infinito è se un ascoltatore su 443 reindirizza indietro a 80 che non è nel mio codice. Spero che abbia un senso. Grazie!
Nick Kotenberg,

3

puoi usare il modulo "net" per ascoltare HTTP e HTTPS sulla stessa porta

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)

5
Quando eseguo questo su Nodo 0.8, sembra rispondere solo l'ultimo server che chiama .listen. In questo caso, HTTP funziona, ma non HTTPS. Se invertisco l'ordine di .createServer, HTTP funziona ma non HTTPS. :(
Joe,

1
Questo non funziona come descritto. Posso confermare il problema che Joe ha visto.
Stepan Mazurov,

2

Questo ha funzionato per me:

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

Questo può essere giusto, ma dovresti approfondire come questo risponde alla domanda del richiedente.
Joe C


0

Questo ha funzionato per me:

/* 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);
    }
});

Consiglia di aggiungere le intestazioni prima di reindirizzare a https

Ora, quando lo fai:

curl http://127.0.0.1 --include

Ottieni:

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/

Uso express 4.17.1

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.