Automatyczne połączenie / przekierowanie HTTPS z node.js / express

182

Próbowałem skonfigurować protokół HTTPS z projektem node.js, nad którym pracuję. Zasadniczo postępowałem zgodnie z dokumentacją node.js dla tego przykładu:

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

Teraz, kiedy to zrobię

curl -k https://localhost:8000/

dostaję

hello world

zgodnie z oczekiwaniami. Ale jeśli to zrobię

curl -k http://localhost:8000/

dostaję

curl: (52) Empty reply from server

Z perspektywy czasu wydaje się to oczywiste, że to zadziała w ten sposób, ale jednocześnie ludzie, którzy ostatecznie odwiedzą mój projekt, nie będą wpisywać https : // yadayada, a ja chcę, aby cały ruch był https od momentu, w którym trafią Strona.

Jak mogę sprawić, aby węzeł (i Express, ponieważ jest to platforma, której używam), przekazywał cały ruch przychodzący do https, niezależnie od tego, czy został określony, czy nie? Nie udało mi się znaleźć żadnej dokumentacji, która dotyczyłaby tego. A może po prostu zakłada się, że w środowisku produkcyjnym node ma coś, co znajduje się przed nim (np. Nginx), co obsługuje tego rodzaju przekierowanie?

To moja pierwsza przygoda z tworzeniem stron internetowych, więc proszę wybacz moją ignorancję, jeśli jest to coś oczywistego.

Jake
źródło
Odpowiedziałem na to zwięźle tutaj: stackoverflow.com/a/23894573/1882064
arcseldon
3
dla każdego, kto ZASTĘPUJE W HEROKU, odpowiedzi w tym pytaniu nie pomogą (dostaniesz „zbyt wiele przekierowań”), ale ta odpowiedź z innego pytania będzie
Rico Kahler

Odpowiedzi:

179

Ryan, dzięki za wskazanie mi właściwego kierunku. Uzupełniłem twoją odpowiedź (akapit drugi) trochę kodem i działa. W tym scenariuszu te fragmenty kodu są umieszczane w mojej aplikacji ekspresowej:

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

Serwer https express nasłuchuje ATM na 3000. Ustawiłem te reguły iptables, aby węzeł nie musiał działać jako 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

Wszystko razem działa dokładnie tak, jak chciałem.

Jake
źródło
15
Naprawdę ważne pytanie (dotyczące bezpieczeństwa). Czy możliwe jest, aby osoba atakująca wykryła i wykradła plik cookie (identyfikator sesji), zanim faktycznie nastąpi przekierowanie?
Costa
4
Jak naprawić ten wynikowy objaw? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
bodine
16
Właściwie to wydaje się lepsze , ... po prostu zawiń przekierowanie zif(!req.secure){}
bodine
6
Chcę tylko wskazać odpowiedź na ważne pytanie @ Costy dotyczące bezpieczeństwa podane w innym poście - stackoverflow.com/questions/8605720/ ...
ThisClark
2
może chcieć zawinąć wokół if(req.protocol==='http')oświadczenia
maksymalnie
120

Jeśli podążasz za konwencjonalnymi portami, ponieważ HTTP domyślnie próbuje portu 80, a HTTPS domyślnie próbuje portu 443, możesz po prostu mieć dwa serwery na tej samej maszynie: Oto kod:

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

Testuj za pomocą https:

$ curl https://127.0.0.1 -k
secure!

Z 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

Więcej szczegółów: Nodejs HTTP i HTTPS przez ten sam port

basarat
źródło
4
res.writeHead(301, etc.)będzie działać poprawnie tylko dla wywołań GET, ponieważ 301nie mówi klientowi, aby używał tej samej metody. Jeśli chcesz zachować używaną metodę (i wszystkie inne parametry), musisz użyć res.writeHead(307, etc.). A jeśli nadal nie działa, być może trzeba będzie wykonać pewne proxy. Źródło: http://stackoverflow.com/a/17612942/1876359
ocramot
113

Dzięki temu facetowi: 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
źródło
11
To najlepsza odpowiedź!
Steffan
3
Zgoda, powinna być pierwszą odpowiedzią.
1984,
12
Poniższa linia pomogła mi w tym rozwiązaniu:app.enable('trust proxy');
arbel03
2
Jak wspomniano powyżej ^^^^: „Trust proxy” jest wymagany, jeśli znajdujesz się za jakimkolwiek serwerem proxy, zaporą ogniową lub systemem równoważenia obciążenia, który przekierowuje ruch: np. Równoważenie obciążenia AWS, które wysyła wszystkie żądania http i https do tego samego portu innego niż główny (np. 3000) na serwerze internetowym VPC.
alanwaring
1
do użycia AWS ELB app.get('X-Forwarded-Proto') != 'http'zamiast req.secure aws.amazon.com/premiumsupport/knowledge-center/ ...
shak
25

Dzięki Nginx możesz skorzystać z nagłówka „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);  
}
ccyrille
źródło
5
Uważam, że req.headers ["x-forwarded-proto"] === "https") nie jest wiarygodne, jednak req.secure działa!
Zugwalt
Znalazłem to samo, wydaje się to bardzo niewiarygodne.
dacopenhagen
2
req.secure jest właściwym sposobem, jednak jest to błędne, jeśli jesteś za proxy, ponieważ req.secure jest równoważne z proto == "https", jednak za proxy express można powiedzieć, że twój proto to https, http
dacopenhagen
7
Musisz app.enable('trust proxy');: „Wskazuje, że aplikacja znajduje się za przednim serwerem proxy i aby użyć nagłówków X-Forwarded- * do określenia połączenia i adresu IP klienta”. expressjs.com/en/4x/api.html#app.set
dskrvk
Nie traktuj adresów URL jako ciągów, zamiast tego użyj modułu url.
arboreal84
12

Od 0.4.12 nie mamy prawdziwego, czystego sposobu nasłuchiwania HTTP i HTTPS na tym samym porcie przy użyciu serwerów HTTP / HTTPS węzła.

Niektórzy ludzie rozwiązali ten problem, mając serwer HTTPS Node (działa to również z Express.js) nasłuchujący 443 (lub jakiś inny port), a także mając mały serwer http, który łączy się z 80 i przekierowuje użytkowników do bezpiecznego portu.

Jeśli absolutnie musisz mieć możliwość obsługi obu protokołów na jednym porcie, musisz umieścić nginx, lighttpd, apache lub inny serwer WWW na tym porcie i działać jako zwrotne proxy dla Node.

Ryan Olds
źródło
Dzięki Ryan. Przez „... mieć mały serwer http, powiązać z 80 i przekierować ...” Zakładam, że masz na myśli inny serwer http węzła . Myślę, że mam pomysł, jak to może działać, ale czy możesz wskazać jakiś przykładowy kod? Ponadto, czy wiesz, czy gdziekolwiek w mapie drogowej węzła znajduje się „czystsze” rozwiązanie tego problemu?
Jake
Twoja aplikacja Node.js może mieć wiele serwerów http (s). Sprawdziłem problemy z Node.js ( github.com/joyent/node/issues ) i listę mailingową ( groups.google.com/group/nodejs ) i nie widziałem żadnych zgłoszonych problemów, ale widziałem kilka postów o problemie na liście mailingowej. O ile wiem, nie ma tego w rurze. Poleciłbym zgłosić to na github i sprawdzić, jakie opinie otrzymujesz.
Ryan Olds
Naprawdę ważne pytanie (dotyczące bezpieczeństwa). Czy możliwe jest, aby osoba atakująca wyszukała i wykradła plik cookie (identyfikator sesji), zanim faktycznie nastąpi przekierowanie?
Costa
Tak, kod stanu 3xx i prawdopodobnie nagłówek lokalizacji są odsyłane z powrotem do agenta, po czym agent żąda adresu URL określonego w nagłówku lokalizacji.
Ryan Olds
Jest rok 2015, czy nadal tak jest?
TheEnvironmentalist
10

Możesz użyć modułu 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);
Mateus Ferreira
źródło
1
Używaj tego ostrożnie, ponieważ pakiet nie był aktualizowany od lat. Napotkałem problemy w AWS, gdzie kodowanie było złe.
Martavis P.
1
pakiet siostrzany: npmjs.com/package/express-to-https - ale nie mam pojęcia, czy działa / isbetter / etc
quetzalcoatl
Należy pamiętać, że jeśli używasz oprogramowania pośredniczącego do obsługi plików statycznych (w tym aplikacji jednostronicowych), najpierw należy dołączyć wszelkie oprogramowanie pośredniczące przekierowania app. (patrz odpowiedź )
Matthew R.
9

Korzystam z rozwiązania zaproponowanego przez Basarata, ale muszę też nadpisać port, ponieważ miałem 2 różne porty dla protokołów HTTP i HTTPS.

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

Wolę również używać niestandardowego portu, aby uruchomić nodejs bez uprawnień roota. Lubię 8080 i 8443, ponieważ pochodzę z wielu lat programowania na tomcat.

Mój kompletny plik to

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

Następnie używam iptable do formułowania ruchu 80 i 443 na moich portach HTTP i 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
Lorenzo Eccher
źródło
Dzięki Lorenzo, to zadziałało dla mnie. Zamiast korzystać z opcji, użyłem letencrypt. Usunąłem opcje var. Dodałem parametry letsencrypt (privateKey, certificate, ca i credentials) i zmieniłem opcje na poświadczenia: https.createServer (poświadczenia, aplikacja)
Nhon Ha
8

Ta odpowiedź musi zostać zaktualizowana, aby działała z Express 4.0. Oto jak uruchomiłem oddzielny serwer http:

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);
NoBrainer
źródło
1
Mam to do pracy, zmieniając httpApp.use ('*', httpRouter); do httpApp.use („/”, httpRouter); i przenosząc go do linii przed utworzeniem serwera hhtp.
KungWaz
8

Jeśli Twoja aplikacja znajduje się za zaufanym proxy (np. AWS ELB lub poprawnie skonfigurowanym nginx), ten kod powinien działać:

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

Uwagi:

  • Zakłada się, że hostujesz swoją witrynę na 80 i 443, jeśli nie, musisz zmienić port podczas przekierowania
  • Zakłada się również, że zamykasz SSL na serwerze proxy. Jeśli robisz SSL od końca do końca, użyj odpowiedzi z @basarat powyżej. Lepszym rozwiązaniem jest protokół SSL typu end-to-end.
  • app.enable ('trust proxy') umożliwia ekspresowe sprawdzenie nagłówka X-Forwarded-Proto
Jamie McCrindle
źródło
6

Uważam, że req.protocol działa, gdy używam express (nie testowałem bez, ale podejrzewam, że działa). przy użyciu bieżącego węzła 0.10.22 z wyrażeniem 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();
  } 
});
Katalizator
źródło
5

Większość odpowiedzi sugeruje użycie nagłówka req.headers.host.

Nagłówek hosta jest wymagany przez HTTP 1.1, ale w rzeczywistości jest opcjonalny, ponieważ nagłówek może nie zostać wysłany przez klienta HTTP, a węzeł / express zaakceptuje to żądanie.

Możesz zapytać: który klient HTTP (np. Przeglądarka) może wysłać żądanie bez tego nagłówka? Protokół HTTP jest bardzo trywialny. Możesz utworzyć żądanie HTTP w kilku wierszach kodu, aby nie wysyłać nagłówka hosta, a jeśli za każdym razem, gdy otrzymasz zniekształcone żądanie, zgłosisz wyjątek i w zależności od tego, jak obsłużysz takie wyjątki, może to spowodować wyłączenie serwera.

Dlatego zawsze sprawdzaj poprawność wszystkich danych wejściowych . To nie jest paranoja, otrzymałem żądania bez nagłówka hosta w mojej usłudze.

Nigdy też nie traktuj adresów URL jako ciągów . Użyj modułu node url, aby zmodyfikować określone części ciągu. Traktowanie adresów URL jako ciągów znaków można wykorzystać na wiele różnych sposobów. Nie rób tego.

arboreal84
źródło
Twoja odpowiedź byłaby doskonała, gdybyś podał przykład.
SerG
Brzmi dobrze, ale niektóre odniesienia / linki byłyby świetne.
Giwan
3
var express = require('express');
var app = express();

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

app.listen(80);

Tego używamy i działa świetnie!

Nick Kotenberg
źródło
1
Nie traktuj adresów URL jako ciągów, zamiast tego użyj modułu url.
arboreal84
4
Podaj przykłady z poczuciem odpowiedzialności. Przepełnienie stosu to gra telefoniczna.
arboreal84
1
to nie spowoduje nieskończonej pętli?
Muhammad Umer
Nie, ponieważ nasłuchuje tylko na porcie 80 i wysyła do portu 443. Jedynym sposobem na nieskończoną pętlę jest to, że jakiś odbiornik na 443 przekierowuje z powrotem do 80, którego nie ma w moim kodzie. Mam nadzieję, że to ma sens. Dzięki!
Nick Kotenberg
3

możesz użyć modułu "net" do nasłuchiwania HTTP i HTTPS na tym samym porcie

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
źródło
5
Kiedy uruchamiam to na Node 0.8, tylko ostatni serwer, który wywołuje .listen wydaje się odpowiadać. W takim przypadku działa HTTP, ale nie HTTPS. Jeśli odwrócę kolejność .createServer, wtedy HTTP działa, ale nie HTTPS. :(
Joe
1
To nie działa zgodnie z opisem. Mogę potwierdzić problem, który zobaczył Joe.
Stepan Mazurov
2

To zadziałało dla mnie:

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
źródło
Może to prawda, ale powinieneś wyjaśnić, jak to odpowiada na pytanie pytającego.
Joe C
1

Możesz utworzyć instancję 2 serwerów Node.js - jeden dla HTTP i HTTPS

Możesz także zdefiniować funkcję konfiguracyjną, którą będą wykonywać oba serwery, dzięki czemu nie musisz pisać zbyt wielu zduplikowanych kodów.

Oto sposób, w jaki to zrobiłem: (używając restify.js, ale powinno działać dla express.js lub samego węzła)

http://qugstart.com/blog/node-js/node-js-restify-server-with-both-http-and-https/

awaage
źródło
0

To zadziałało dla mnie:

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

Zalecamy dodanie nagłówków przed przekierowaniem do https

Teraz, kiedy to zrobisz:

curl http://127.0.0.1 --include

Dostajesz:

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/

Używam express 4.17.1

χρηστος απατσιδης
źródło