Node.js / express ile otomatik HTTPS bağlantısı / yönlendirme


182

Üzerinde çalıştığım bir node.js projesi ile HTTPS kurmaya çalışıyorum. Aslında bu örnek için node.js belgelerini izledim :

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

Şimdi, yaptığımda

curl -k https://localhost:8000/

alırım

hello world

beklenildiği gibi. Ama eğer yaparsam

curl -k http://localhost:8000/

alırım

curl: (52) Empty reply from server

Geriye dönük olarak bu, bu şekilde çalışacağı açık gibi görünüyor, ancak aynı zamanda projemi sonunda ziyaret eden insanlar https : // yadayada yazmayacaklar ve tüm trafiğin vuruldukları andan itibaren https olmasını istiyorum site.

Belirtilmiş olup olmadığına bakılmaksızın, gelen tüm trafiği https'ye dağıtmak için düğümü (ve kullandığım çerçeve olduğu gibi Express'i) nasıl alabilirim? Bunu ele alan herhangi bir belge bulamadım. Yoksa sadece bir üretim ortamında, düğümün önünde bu tür bir yönlendirmeyi işleyen bir şey (örneğin, nginx) olduğu var mı?

Bu benim web geliştirmeye ilk adımım, bu yüzden bu bariz bir şeyse lütfen cehaletimi affet.


Bunu kısaca burada cevapladınız: stackoverflow.com/a/23894573/1882064
arcseldon

3
HEROKU'YA DEPLOYING yapan herkes için, bu sorunun yanıtları size yardımcı olmaz ("çok fazla yönlendirme alırsınız"), ancak başka bir sorudan gelen bu cevap
Rico Kahler

Yanıtlar:


179

Ryan, beni doğru yöne yönlendirdiğin için teşekkürler. Cevabınızı (2. paragraf) biraz kodla ettim ve işe yarıyor. Bu senaryoda bu kod parçacıkları benim ekspres app koymak:

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

Https ekspres sunucusu ATM'yi 3000'de dinler. Düğümün root olarak çalışması gerekmeyecek şekilde bu iptables kurallarını ayarladım:

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

Hep birlikte, bu tam istediğim gibi çalışıyor.


15
Gerçekten önemli bir soru (güvenlikle ilgili). Bu yönlendirme gerçekleşmeden önce, bir saldırganın koklamasını ve bir çerezi (oturum kimliği) çalması mümkün mü?
Costa

4
Ortaya çıkan bu semptomu nasıl düzeltebilirim? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
bodine

16
Aslında, bu daha iyi görünüyor , ... sadece yönlendirmeyi sarınif(!req.secure){}
bodine

6
Sadece @ Costa'nın başka bir yazıda verilen güvenlikle ilgili önemli sorunun cevabına dikkat çekmek istiyorum - stackoverflow.com/questions/8605720/…
ThisClark

2
bir if(req.protocol==='http')ifadeyi sarmak isteyebilir
Max

120

HTTP varsayılan olarak 80 numaralı bağlantı noktasını denediğinden ve HTTPS varsayılan olarak 443 numaralı bağlantı noktasını denediğinden geleneksel bağlantı noktalarını izlerseniz, aynı makinede iki sunucunuz olabilir: İşte 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);

Https ile test edin:

$ curl https://127.0.0.1 -k
secure!

Http ile:

$ 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

Daha fazla detay: Aynı bağlantı noktası üzerinden Nodejs HTTP ve HTTPS


4
res.writeHead(301, etc.)301istemciye aynı yöntemi kullanmasını söylemediği için yalnızca GET çağrıları için doğru şekilde çalışacaktır . Kullanılan yöntemi (ve diğer tüm parametreleri) korumak istiyorsanız kullanmak zorundasınız res.writeHead(307, etc.). Ve hala işe yaramazsa, biraz proxy yapmak zorunda kalabilirsiniz. Kaynak: http://stackoverflow.com/a/17612942/1876359
ocramot

113

Bu adam için teşekkürler: 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
Bu en iyi cevap!
Steffan

3
Anlaşılan, ilk cevap olmalı.
1984

12
Aşağıdaki satır bu çözümün benim için çalışmasına yardımcı oldu:app.enable('trust proxy');
arbel03

2
Yukarıda belirtildiği gibi ^ ^ ^ ^: Trafiği yönlendiren herhangi bir proxy, güvenlik duvarı veya yük dengeleyicisinin arkasındaysanız 'trust proxy' gereklidir: örn. Tüm http ve https isteklerini aynı kök olmayan bağlantı noktasına gönderen AWS yük dengeleme (ör. 3000) VPC'nizin web sunucusunda.
alanwaring

1
aws.amazon.com/premiumsupport/knowledge-center/…app.get('X-Forwarded-Proto') != 'http' yerine AWS ELB kullanımı içinreq.secure
21'de sallayın

25

Nginx ile "x-forwarded-proto" başlığından yararlanabilirsiniz:

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

5
Ben güvenilir değil req.headers ["x-forwarded-proto"] === "https") bulundu, ancak req.secure çalışır!
Zugwalt

Aynı şeyi buldum, bu çok güvenilmez görünüyor.
dacopenhagen

2
req.secure doğru yoldur, ancak bir proxy arkasındaysanız bu hatalıdır çünkü req.secure proto == "https" ile eşdeğerdir, ancak bir proxy ekspresinin arkasında
proto'unuzun

7
Yapmanız app.enable('trust proxy');gerekenler: "Uygulamanın öne bakan bir proxy'nin arkasında olduğunu ve istemcinin bağlantısını ve IP adresini belirlemek için X-Forwarded- * başlıklarını kullandığını belirtir." expressjs.com/tr/4x/api.html#app.set
dskrvk

URL'lere dize gibi davranmayın, bunun yerine url modülünü kullanın.
arboreal84

12

0.4.12'den itibaren Düğümün HTTP / HTTPS sunucularını kullanarak aynı bağlantı noktasında HTTP ve HTTPS dinlemenin gerçek temiz bir yolu yoktur.

Bazı insanlar Node'nin HTTPS sunucusunu (bu Express.js ile de çalışır) 443'ü (veya başka bir bağlantı noktasını) dinleyerek ve ayrıca 80'e bağlanan ve kullanıcıları güvenli bağlantı noktasına yönlendiren küçük bir http sunucusuna sahip olarak bu sorunu çözdüler.

Kesinlikle tek bir bağlantı noktasında her iki protokolü de kullanabilmeniz gerekiyorsa, o bağlantı noktasına nginx, lighttpd, apache veya başka bir web sunucusu koymanız ve Düğüm için ters proxy olarak davranmanız gerekir.


Teşekkürler Ryan. "... 80'e bağlanmak ve yönlendirmek için küçük bir http sunucusu var ..." ile başka bir düğüm http sunucusu demek istediğinizi varsayalım . Bunun nasıl çalışabileceğine dair bir fikrim var ama herhangi bir örnek kodu gösterebilir misiniz? Ayrıca, bu sorun için "temiz" bir çözüm düğüm yol haritası herhangi bir yerinde olup olmadığını biliyor musunuz?
Jake

Node.js uygulamanızın birden çok http (s) sunucusu olabilir. Node.js sorunlarını ( github.com/joyent/node/issues ) ve posta listesini ( groups.google.com/group/nodejs ) kontrol ettim ve bildirilen herhangi bir sorun görmedim, ancak sorunla ilgili birkaç gönderi gördüm posta listesinde. Bildiğim kadarıyla bu boruda değil. Github'da rapor etmenizi ve hangi geri bildirimleri aldığınızı görmenizi tavsiye ederim.
Ryan Olds

Gerçekten önemli bir soru (güvenlikle ilgili). Bu yönlendirme gerçekleşmeden önce, bir "saldırganın" koklaması ve bir çerezi çalması (oturum kimliği) mümkün müdür?
Costa

Evet, aracıya bir 3xx durum kodu ve muhtemelen bir Konum başlığı gönderilir; bu noktada aracı, Konum başlığı tarafından belirtilen URL'yi ister.
Ryan Olds

2015, hala böyle mi?
TheEnvironmentalist

10

Sen kullanabilirsiniz ekspres-kuvvet-https modülü:

npm install --save express-force-https

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

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

1
Paket yıllardır güncellenmediği için bunu dikkatli kullanın. AWS'de kodlamanın kötü olduğu sorunlarla karşılaştım.
Martavis P.

1
kardeş paket: npmjs.com/package/express-to-https - ama işe yarayıp yaramadığı hakkında hiçbir fikrim yok / isbetter / etc
quetzalcoatl

Statik dosyaları sunmak için ara katman yazılımı kullanıyorsanız (tek sayfalık uygulamalar dahil), önce tüm yönlendirme ara katman yazılımlarının iliştirilmesi gerektiğini unutmayın app. ( bu cevaba bakınız )
Matthew R.

9

Basarat tarafından önerilen çözümü kullanıyorum, ancak HTTP ve HTTPS protokolleri için 2 farklı bağlantı noktasına sahip olduğum için bağlantı noktasının üzerine yazmam gerekiyor.

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

Ben de kök ayrıcalıkları olmadan nodejs başlatmak için standart port kullanmayı tercih ederim. 8080 ve 8443'ü seviyorum çünkü tomcat üzerinde yıllarca süren programlardan geldim.

Tam dosyam oldu

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

Sonra HTTP ve HTTPS bağlantı noktalarımda 80 ve 443 trafiğini önceden yazmak için iptable kullanıyorum.

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

Teşekkürler Lorenzo, bu benim için çalıştı. Seçenekleri kullanmak yerine letsencrypt kullandım. Var seçeneklerini sildim. Letsencrypt parametrelerini (privateKey, sertifika, ca ve kimlik bilgileri) ekledim ve seçenekleri kimlik bilgilerine değiştirdim: https.createServer (kimlik bilgileri, uygulama)
Nhon Ha

8

Express 4.0 ile çalışmak için bu yanıtın güncellenmesi gerekir. İşte nasıl çalışmak için ayrı http sunucusu var:

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
Bu httpApp.use ('*', httpRouter) değiştirerek çalışmak için var; httpApp.use'a ('/', httpRouter); ve hhtp sunucusunu oluşturmadan önce satıra taşıyın.
KungWaz

8

Uygulamanız güvenilir bir proxy'nin arkasındaysa (örn. AWS ELB veya doğru yapılandırılmış bir nginx), bu kodun çalışması gerekir:

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

Notlar:

  • Bu, sitenizi 80 ve 443'te barındırdığınızı varsayarsa, yeniden yönlendirdiğinizde bağlantı noktasını değiştirmeniz gerekir
  • Bu ayrıca proxy'deki SSL'yi sonlandırdığınızı varsayar. Uçtan uca SSL yapıyorsanız, yukarıdaki @basarat'ın yanıtını kullanın. Uçtan uca SSL daha iyi bir çözümdür.
  • app.enable ('trust proxy'), express'in X-Forwarded-Proto başlığını kontrol etmesini sağlar

6

Ben ekspres kullandığımda req.protocol çalışır buluyorum (olmadan test etmedim ama çalışır şüpheli). ifade 3.4.3 ile geçerli düğüm 0.10.22 kullanma

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

5

Buradaki yanıtların çoğu req.headers.host üstbilgisini kullanmanızı önerir.

Ana bilgisayar üstbilgisi HTTP 1.1 tarafından zorunludur, ancak üstbilgi aslında bir HTTP istemcisi tarafından gönderilmeyebileceğinden ve düğüm / ifade bu isteği kabul edeceğinden aslında isteğe bağlıdır.

Şunları sorabilirsiniz: Hangi HTTP istemcisi (örneğin: tarayıcı) bu üstbilgiyi eksik bir istek gönderebilir? HTTP protokolü çok önemsizdir. Bir ana bilgisayar üstbilgisi göndermemek için birkaç kod satırında bir HTTP isteği oluşturabilirsiniz ve hatalı biçimlendirilmiş her istek aldığınızda bir istisna atarsınız ve bu tür istisnaları nasıl ele aldığınıza bağlı olarak bu sunucunuzu devre dışı bırakabilir.

Bu yüzden her zaman tüm girdileri doğrulayın . Bu paranoya değil, hizmetimde ana bilgisayar üstbilgisi eksik istekleri aldım.

Ayrıca, URL'lere asla dizgi olarak davranmayın . Bir dizenin belirli bölümlerini değiştirmek için düğüm url modülünü kullanın. URL'leri dize olarak ele almak birçok farklı şekilde kullanılabilir. Yapma.


Bir örnek vermiş olsaydınız, cevabınız mükemmel olurdu.
SerG

Kulağa yasal geliyor ama bazı referanslar / bağlantılar harika olurdu.
Giwan

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

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

app.listen(80);

Kullandığımız şey bu ve harika çalışıyor!


1
URL'lere dize gibi davranmayın, bunun yerine url modülünü kullanın.
arboreal84

4
Sorumluluk duygusu ile örnekler verin. Yığın taşması telefon oyunudur.
arboreal84

1
Bu sonsuz döngüye neden olmaz?
Muhammed Umer

Hayır, çünkü yalnızca 80 numaralı bağlantı noktasını dinler ve 443 numaralı bağlantı noktasına gönderir. 443 üzerindeki bazı dinleyiciler kodumda olmayan 80'e geri yönlendirirse, sonsuz döngü gerçekleşir. Umarım bu mantıklıdır. Teşekkürler!
Nick Kotenberg

3

aynı portta HTTP ve HTTPS dinlemek için "net" modülünü kullanabilirsiniz

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
Düğüm 0.8 üzerinde çalıştırdığımda, yalnızca .listen çağıran son sunucu yanıt gibi görünüyor. Bu durumda, HTTP çalışır, ancak HTTPS çalışmaz. Ben .createServer sırasını tersine çevirirseniz, HTTP çalışır ama HTTPS değil. :(
Joe

1
Bu tarif edildiği gibi çalışmaz. Joe'nun gördüğü sorunu teyit edebilirim.
Stepan Mazurov

2

Bu benim için çalıştı:

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

Bu doğru olabilir, ancak bunun sorucunun sorusuna nasıl cevap verdiğini açıklamalısınız.
Joe C


0

Bu benim için çalıştı:

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

Https'ye yönlendirmeden önce üstbilgileri eklemenizi öneririz

Şimdi, yaptığınız zaman:

curl http://127.0.0.1 --include

Şunları elde edersiniz:

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/

Express 4.17.1 kullanıyorum

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.