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.


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.


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


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

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

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.


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

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

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

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

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

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.


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!


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)

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

Może to prawda, ale powinieneś wyjaśnić, jak to odpowiada na pytanie pytającego.
Joe C


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.