Connexion / redirection HTTPS automatique avec node.js / express


182

J'ai essayé de configurer HTTPS avec un projet node.js sur lequel je travaille. J'ai essentiellement suivi la documentation de node.js pour cet exemple:

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

Maintenant, quand je fais

curl -k https://localhost:8000/

Je reçois

hello world

comme prévu. Mais si je fais

curl -k http://localhost:8000/

Je reçois

curl: (52) Empty reply from server

Rétrospectivement, cela semble évident que cela fonctionnerait de cette façon, mais en même temps, les personnes qui finissent par visiter mon projet ne vont pas taper https : // yadayada, et je veux que tout le trafic soit https à partir du moment où ils arrivent. le site.

Comment puis-je obtenir node (et Express car c'est le framework que j'utilise) pour transférer tout le trafic entrant vers https, qu'il ait été spécifié ou non? Je n'ai pu trouver aucune documentation traitant de cela. Ou est-il simplement supposé que dans un environnement de production, un nœud a quelque chose devant lui (par exemple nginx) qui gère ce type de redirection?

C'est ma première incursion dans le développement Web, alors pardonnez mon ignorance si c'est quelque chose d'évident.


J'ai répondu succinctement à cette question ici: stackoverflow.com/a/23894573/1882064
arcseldon

3
pour toute personne DÉPLOYANT VERS HEROKU, les réponses à cette question ne vous aideront pas (vous obtiendrez "trop ​​de redirections") mais cette réponse d'une autre question le fera
Rico Kahler

Réponses:


179

Ryan, merci de m'avoir pointé dans la bonne direction. J'ai étoffé un peu votre réponse (2ème paragraphe) avec du code et cela fonctionne. Dans ce scénario, ces extraits de code sont placés dans mon application 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);

Le serveur https express écoute ATM sur 3000. J'ai configuré ces règles iptables afin que le nœud n'ait pas à s'exécuter en tant que 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

Dans l'ensemble, cela fonctionne exactement comme je le voulais.


15
Question vraiment importante (concernant la sécurité). Avant que cette redirection ne se produise, est-il possible pour un attaquant de détecter et de voler un cookie (identifiant de session)?
Costa

4
Comment corriger ce symptôme résultant? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
bodine

16
En fait, cela semble mieux , ... enveloppez simplement la redirection avecif(!req.secure){}
bodine

6
Je veux juste souligner la réponse à la question importante de @ Costa sur la sécurité donnée dans un autre article - stackoverflow.com/questions/8605720/…
ThisClark

2
pourrait vouloir conclure une if(req.protocol==='http')déclaration
Max

120

Si vous suivez les ports conventionnels puisque HTTP essaie le port 80 par défaut et HTTPS essaie le port 443 par défaut, vous pouvez simplement avoir deux serveurs sur la même machine: Voici le 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);

Tester avec https:

$ curl https://127.0.0.1 -k
secure!

Avec 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

Plus de détails: Nodejs HTTP et HTTPS sur le même port


4
res.writeHead(301, etc.)ne fonctionnera correctement que pour les appels GET, car il 301ne dit pas au client d'utiliser la même méthode. Si vous souhaitez conserver la méthode utilisée (et tous les autres paramètres), vous devez utiliser res.writeHead(307, etc.). Et si cela ne fonctionne toujours pas, vous devrez peut-être effectuer un proxy. Source: http://stackoverflow.com/a/17612942/1876359
ocramot

113

Merci à ce gars: 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
C'est la meilleure réponse!
Steffan

3
D'accord, devrait être la première réponse.
1984

12
La ligne suivante a aidé cette solution à fonctionner pour moi:app.enable('trust proxy');
arbel03

2
Comme indiqué ci-dessus ^^^^: `` proxy de confiance '' est requis si vous êtes derrière tout type de proxy, pare-feu ou équilibreur de charge qui redirige le trafic: par exemple, l'équilibrage de charge AWS qui envoie toutes les requêtes http & https vers le même port non root (par exemple, 3000) sur le serveur Web de votre VPC.
alanwaring

1
pour AWS ELB utiliser app.get('X-Forwarded-Proto') != 'http'au lieu de req.secure aws.amazon.com/premiumsupport/knowledge-center
shak

25

Avec Nginx, vous pouvez profiter de l'en-tête "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
J'ai trouvé que req.headers ["x-forwarded-proto"] === "https") n'était pas fiable, mais req.secure fonctionne!
Zugwalt

J'ai trouvé la même chose, cela semble très peu fiable.
dacopenhagen

2
req.secure est le bon moyen, cependant c'est bogué si vous êtes derrière un proxy parce que req.secure est équivalent à proto == "https", cependant derrière un proxy express peut dire que votre proto est https, http
dacopenhagen

7
Vous devez app.enable('trust proxy');: "Indique que l'application se trouve derrière un proxy frontal et utiliser les en-têtes X-Forwarded- * pour déterminer la connexion et l'adresse IP du client." expressjs.com/en/4x/api.html#app.set
dskrvk

Ne traitez pas les URL comme des chaînes, utilisez plutôt le module url.
arboreal84

12

Depuis la version 0.4.12, nous n'avons pas vraiment de moyen propre d'écouter HTTP et HTTPS sur le même port en utilisant les serveurs HTTP / HTTPS de Node.

Certaines personnes ont résolu ce problème en demandant au serveur HTTPS de Node (cela fonctionne également avec Express.js) d'écouter 443 (ou d'un autre port) et d'avoir également un petit serveur http lié à 80 et de rediriger les utilisateurs vers le port sécurisé.

Si vous devez absolument être capable de gérer les deux protocoles sur un seul port, vous devez mettre nginx, lighttpd, apache ou un autre serveur Web sur ce port et agir en tant que proxy inverse pour Node.


Merci Ryan. Par "... avoir un petit serveur http lié à 80 et rediriger ..." Je suppose que vous voulez dire un autre serveur http de nœud . Je pense que j'ai une idée de la façon dont cela pourrait fonctionner, mais pouvez-vous indiquer un exemple de code? En outre, savez-vous si une solution «plus propre» à ce problème se trouve n'importe où dans la feuille de route du nœud?
Jake

Votre application Node.js peut avoir plusieurs serveurs http (s). J'ai vérifié les problèmes de Node.js ( github.com/joyent/node/issues ) et la liste de diffusion ( groups.google.com/group/nodejs ) et je n'ai vu aucun problème signalé, mais j'ai vu quelques articles sur le problème sur la liste de diffusion. Pour autant que je sache, ce n'est pas dans le tuyau. Je recommanderais de le signaler sur github et de voir quels commentaires vous obtenez.
Ryan Olds

Question vraiment importante (concernant la sécurité). Avant que cette redirection ne se produise, est-il possible pour un "attaquant" de détecter et de voler un cookie (identifiant de session)?
Costa

Oui, un code d'état 3xx et éventuellement un en-tête Location sont renvoyés à l'agent, auquel point l'agent demande l'URL spécifiée par l'en-tête Location.
Ryan Olds

Nous sommes en 2015, est-ce toujours le cas?
TheEnvironmentalist

10

Vous pouvez utiliser le module 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
Utilisez-le avec prudence car le package n'a pas été mis à jour depuis des années. J'ai rencontré des problèmes dans AWS où le codage était mauvais.
Martavis P.

1
paquet soeur: npmjs.com/package/express-to-https - mais je n'ai aucune idée si cela fonctionne / isbetter / etc
quetzalcoatl

Notez que si vous utilisez un middleware pour servir des fichiers statiques (y compris des applications d'une seule page), tout middleware de redirection doit être attaché en apppremier. (voir cette réponse )
Matthew R.

9

J'utilise la solution proposée par Basarat mais j'ai aussi besoin d'écraser le port car j'avais l'habitude d'avoir 2 ports différents pour les protocoles HTTP et HTTPS.

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

Je préfère également utiliser un port non standard pour démarrer nodejs sans privilèges root. J'aime 8080 et 8443 parce que je viens de beaucoup d'années de programmation sur tomcat.

Mon dossier complet devient

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

Ensuite, j'utilise iptable pour forwording 80 et 443 trafic sur mes ports HTTP et 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

Merci Lorenzo, cela a fonctionné pour moi. Au lieu d'utiliser des options, j'ai utilisé letsencrypt. J'ai supprimé les options var. J'ai ajouté les paramètres letsencrypt (privateKey, certificat, CA et informations d'identification) et j'ai changé les options des informations d'identification: https.createServer (informations d'identification, application)
Nhon Ha

8

Cette réponse doit être mise à jour pour fonctionner avec Express 4.0. Voici comment j'ai fait fonctionner le serveur http séparé:

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
J'ai réussi à faire fonctionner cela en modifiant httpApp.use ('*', httpRouter); vers httpApp.use ('/', httpRouter); et le déplacer vers la ligne avant de créer le serveur hhtp.
KungWaz

8

Si votre application se trouve derrière un proxy de confiance (par exemple, un AWS ELB ou un nginx correctement configuré), ce code devrait fonctionner:

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

Remarques:

  • Cela suppose que vous hébergez votre site sur 80 et 443, sinon, vous devrez changer le port lorsque vous redirigez
  • Cela suppose également que vous mettez fin au SSL sur le proxy. Si vous faites du SSL de bout en bout, utilisez la réponse de @basarat ci-dessus. Le SSL de bout en bout est la meilleure solution.
  • app.enable ('trust proxy') permet à express de vérifier l'en-tête X-Forwarded-Proto

6

Je trouve que req.protocol fonctionne lorsque j'utilise express (je n'ai pas testé sans mais je soupçonne que cela fonctionne). en utilisant le nœud actuel 0.10.22 avec 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 plupart des réponses ici suggèrent d'utiliser l'en-tête req.headers.host.

L'en-tête Host est requis par HTTP 1.1, mais il est en fait facultatif puisque l'en-tête peut ne pas être réellement envoyé par un client HTTP, et node / express acceptera cette demande.

Vous pourriez demander: quel client HTTP (par exemple: navigateur) peut envoyer une requête sans cet en-tête? Le protocole HTTP est très simple. Vous pouvez créer une requête HTTP en quelques lignes de code, pour ne pas envoyer d'en-tête d'hôte, et si chaque fois que vous recevez une requête malformée, vous lancez une exception, et selon la façon dont vous gérez ces exceptions, cela peut arrêter votre serveur.

Alors validez toujours toutes les entrées . Ce n'est pas de la paranoïa, j'ai reçu des demandes sans en-tête d'hôte dans mon service.

De plus, ne traitez jamais les URL comme des chaînes . Utilisez le module URL du nœud pour modifier des parties spécifiques d'une chaîne. Traiter les URL comme des chaînes peut être exploité de nombreuses manières. Ne fais pas ça.


Votre réponse serait parfaite si vous aviez donné un exemple.
SerG

Cela semble légitime, mais certaines références / liens seraient excellents.
Giwan

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

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

app.listen(80);

C'est ce que nous utilisons et cela fonctionne très bien!


1
Ne traitez pas les URL comme des chaînes, utilisez plutôt le module url.
arboreal84

4
Donnez des exemples avec un sens des responsabilités. Le débordement de pile est le jeu du téléphone.
arboreal84

1
cela ne causera pas de boucle infinie?
Muhammad Umer

Non, car il n'écoute que sur le port 80 et envoie au port 443. La seule façon pour une boucle infinie de se produire est si un auditeur sur 443 redirige vers 80 qui n'est pas dans mon code. J'espère que cela à du sens. Merci!
Nick Kotenberg

3

vous pouvez utiliser le module "net" pour écouter HTTP et HTTPS sur le même port

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
Quand j'exécute ceci sur Node 0.8, seul le dernier serveur qui appelle .listen semble répondre. Dans ce cas, HTTP fonctionne, mais pas HTTPS. Si j'inverse l'ordre de .createServer, alors HTTP fonctionne mais pas HTTPS. :(
Joe

1
Cela ne fonctionne pas comme décrit. Je peux confirmer le problème que Joe a vu.
Stepan Mazurov

2

Cela a fonctionné pour moi:

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

Cela peut être correct, mais vous devriez expliquer comment cela répond à la question du demandeur.
Joe C


0

Cela a fonctionné pour moi:

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

Recommander d'ajouter les en-têtes avant de rediriger vers https

Maintenant, quand vous faites:

curl http://127.0.0.1 --include

Vous obtenez:

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/

J'utilise express 4.17.1

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.