Koneksi HTTPS otomatis / redirect dengan node.js / express


181

Saya sudah mencoba mengatur HTTPS dengan proyek node.js yang sedang saya kerjakan. Saya pada dasarnya mengikuti dokumentasi node.js untuk contoh ini:

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

Sekarang, ketika saya melakukannya

curl -k https://localhost:8000/

saya mendapat

hello world

seperti yang diharapkan. Tetapi jika saya lakukan

curl -k http://localhost:8000/

saya mendapat

curl: (52) Empty reply from server

Dalam retrospeksi, ini tampak jelas bahwa itu akan bekerja seperti ini, tetapi pada saat yang sama, orang-orang yang akhirnya mengunjungi proyek saya tidak akan mengetikkan https : // yadayada, dan saya ingin semua lalu lintas menjadi https dari saat mereka mencapai situs

Bagaimana saya bisa mendapatkan node (dan Express karena itu adalah kerangka yang saya gunakan) untuk menyerahkan semua lalu lintas masuk ke https, terlepas dari apakah itu ditentukan atau tidak? Saya belum dapat menemukan dokumentasi yang membahas hal ini. Atau hanya diasumsikan bahwa dalam lingkungan produksi, simpul memiliki sesuatu yang duduk di depannya (misalnya nginx) yang menangani pengalihan semacam ini?

Ini adalah perampokan pertama saya ke pengembangan web, jadi tolong maafkan ketidaktahuan saya jika ini sesuatu yang jelas.


Telah menjawab ini dengan ringkas di sini: stackoverflow.com/a/23894573/1882064
arcseldon

3
bagi siapa pun yang BERJALAN ke HEROKU, jawaban dalam pertanyaan ini tidak akan membantu Anda (Anda akan mendapatkan "terlalu banyak arahan ulang") tetapi jawaban ini dari pertanyaan lain akan
Rico Kahler

Jawaban:


179

Ryan, terima kasih telah mengarahkan saya ke arah yang benar. Saya menyempurnakan jawaban Anda (paragraf 2) sedikit dengan beberapa kode dan berfungsi. Dalam skenario ini cuplikan kode ini dimasukkan ke dalam aplikasi kilat saya:

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

Server https express mendengarkan ATM pada 3000. Saya mengatur aturan iptables ini sehingga simpul tidak harus dijalankan sebagai 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

Secara keseluruhan, ini bekerja persis seperti yang saya inginkan.


15
Pertanyaan yang sangat penting (mengenai keamanan). Sebelum pengalihan itu benar-benar terjadi, mungkinkah penyerang mengendus dan mencuri cookie (ID sesi)?
Costa

4
Bagaimana saya memperbaiki gejala yang dihasilkan ini? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
Bodine

16
Sebenarnya, ini tampaknya lebih baik , ... hanya membungkus pengalihan denganif(!req.secure){}
bodine

6
Saya hanya ingin menunjukkan jawaban atas pertanyaan penting @ Costa tentang keamanan yang diberikan di pos lain - stackoverflow.com/questions/8605720/…
ThisClark

2
mungkin ingin membungkus if(req.protocol==='http')pernyataan
Maks

120

Jika Anda mengikuti port konvensional karena HTTP mencoba port 80 secara default dan HTTPS mencoba port 443 secara default, Anda dapat memiliki dua server pada mesin yang sama: Berikut kodenya:

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

Uji dengan https:

$ curl https://127.0.0.1 -k
secure!

Dengan 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

Lebih detail: Nodejs HTTP dan HTTPS melalui port yang sama


4
res.writeHead(301, etc.)hanya akan berfungsi dengan benar untuk panggilan GET, karena 301tidak memberi tahu klien untuk menggunakan metode yang sama. Jika Anda ingin agar metode yang digunakan (dan semua parameter lainnya) harus Anda gunakan res.writeHead(307, etc.). Dan jika itu masih tidak berhasil, Anda bisa melakukan proxy. Sumber: http://stackoverflow.com/a/17612942/1876359
ocramot

113

Terima kasih kepada orang ini: 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
Ini jawaban terbaik!
Steffan

3
Setuju, harus menjadi jawaban pertama.
1984

12
Baris berikut membantu solusi ini bekerja untuk saya:app.enable('trust proxy');
arbel03

2
Seperti yang dinyatakan di atas ^^^^: 'proxy kepercayaan' diperlukan jika Anda berada di belakang proxy, firewall, atau penyeimbang beban apa pun yang mengalihkan lalu lintas: mis. Penyeimbangan muatan AWS yang mengirim semua permintaan http & https ke port non-root yang sama (mis., 3000) di server web VPC Anda.
alanwaring

1
untuk penggunaan AWS ELB app.get('X-Forwarded-Proto') != 'http'alih-alih req.secure aws.amazon.com/premiumsupport/knowledge-center/…
shak

25

Dengan Nginx Anda dapat memanfaatkan header "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
Saya menemukan req.headers ["x-forwarded-proto"] === "https") tidak dapat diandalkan, namun req.secure berfungsi!
Zugwalt

Saya menemukan hal yang sama, ini terlihat sangat tidak dapat diandalkan.
dacopenhagen

2
req.secure adalah cara yang tepat, namun ini buggy jika Anda berada di belakang proxy karena req.secure setara dengan proto == "https", namun di belakang proxy express dapat mengatakan proto Anda adalah https, http
dacopenhagen

7
Anda perlu app.enable('trust proxy');: "Menunjukkan aplikasi berada di belakang proxy yang menghadap ke depan, dan menggunakan header X-Forwarded- * untuk menentukan koneksi dan alamat IP klien." expressjs.com/en/4x/api.html#app.set
dskrvk

Jangan memperlakukan url sebagai string, gunakan modul url sebagai gantinya.
arboreal84

12

Pada 0.4.12 kami tidak memiliki cara nyata untuk mendengarkan HTTP & HTTPS pada port yang sama menggunakan server HTTP / HTTPS Node.

Beberapa orang telah memecahkan masalah ini dengan memiliki server HTTPS Node (ini juga berfungsi dengan Express.js) mendengarkan 443 (atau port lain) dan juga memiliki server http kecil yang diikat ke 80 dan mengarahkan pengguna ke port aman.

Jika Anda benar-benar harus dapat menangani kedua protokol pada satu port maka Anda perlu meletakkan nginx, lighttpd, apache, atau beberapa server web lain pada port tersebut dan telah bertindak sebagai proxy terbalik untuk Node.


Terima kasih Ryan. Dengan "... punya http server kecil mengikat ke 80 dan redirect ..." Saya menganggap Anda maksud server http node lain . Saya pikir saya punya ide tentang bagaimana ini bisa bekerja tetapi dapatkah Anda menunjukkan contoh kode? Juga, apakah Anda tahu apakah solusi "bersih" untuk masalah ini ada di mana saja di peta jalan simpul?
Jake

Aplikasi Node.js Anda dapat memiliki beberapa server http. Saya memeriksa masalah Node.js ( github.com/joyent/node/issues ) dan milis ( groups.google.com/group/nodejs ) dan tidak melihat ada masalah yang dilaporkan, tetapi melihat beberapa posting tentang masalah tersebut di milis. Sejauh yang saya tahu ini tidak ada dalam pipa. Saya akan merekomendasikan melaporkannya di github dan melihat umpan balik apa yang Anda dapatkan.
Ryan Olds

Pertanyaan yang sangat penting (mengenai keamanan). Sebelum pengalihan itu benar-benar terjadi, mungkinkah seorang "penyerang" mengendus dan mencuri cookie (ID sesi)?
Costa

Ya, kode status 3xx dan mungkin tajuk Lokasi dikirim kembali ke agen, di mana agen meminta URL yang ditentukan oleh tajuk Lokasi.
Ryan Olds

Ini tahun 2015, apakah ini masih terjadi?
TheEnvironmentalist

10

Anda dapat menggunakan modul 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
Gunakan ini dengan hati-hati karena paket belum diperbarui dalam beberapa tahun. Saya mengalami masalah di AWS di mana pengkodean buruk.
Martavis P.

1
paket saudara: npmjs.com/package/express-to-https - tapi saya tidak tahu apakah itu berfungsi / isbetter / etc
quetzalcoatl

Perhatikan bahwa jika Anda menggunakan middleware untuk menyajikan file statis (termasuk aplikasi satu halaman), middleware redirect apa pun harus dilampirkan appterlebih dahulu. (lihat jawaban ini )
Matius R.

9

Saya menggunakan solusi yang diusulkan oleh Basarat tapi saya juga perlu menimpa port karena saya dulu punya 2 port berbeda untuk protokol HTTP dan HTTPS.

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

Saya lebih suka juga menggunakan bukan port standar sehingga untuk memulai nodejs tanpa hak root. Saya suka 8080 dan 8443 karena saya berasal dari banyak tahun pemrograman pada kucing jantan.

File lengkap saya menjadi

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

Lalu saya menggunakan iptable untuk menulis 80 dan 443 lalu lintas pada port HTTP dan HTTPS saya.

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

Terima kasih Lorenzo, ini berhasil untuk saya. Alih-alih menggunakan opsi, saya menggunakan letsencrypt. Saya menghapus opsi var. Saya menambahkan parameter letsencrypt (privateKey, sertifikat, ca, dan kredensial), dan saya mengubah opsi menjadi kredensial: https.createServer (kredensial, aplikasi)
Nhon Ha

8

Jawaban ini perlu diperbarui agar berfungsi dengan Express 4.0. Berikut adalah cara kerja server http terpisah:

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
Saya dapat ini berfungsi dengan mengubah httpApp.use ('*', httpRouter); ke httpApp.use ('/', httpRouter); dan memindahkannya ke baris sebelum Anda membuat server hhtp.
KungWaz

8

Jika aplikasi Anda berada di belakang proxy tepercaya (mis. AWS ELB atau nginx yang dikonfigurasi dengan benar), kode ini akan berfungsi:

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

Catatan:

  • Ini mengasumsikan bahwa Anda meng-hosting situs Anda pada 80 dan 443, jika tidak, Anda harus mengubah port ketika Anda mengarahkan ulang
  • Ini juga mengasumsikan bahwa Anda mengakhiri SSL pada proxy. Jika Anda melakukan SSL ujung ke ujung, gunakan jawaban dari @basarat di atas. SSL ujung ke ujung adalah solusi yang lebih baik.
  • app.enable ('proxy kepercayaan') memungkinkan express untuk memeriksa header X-Forwarded-Proto

6

Saya menemukan req.protocol berfungsi ketika saya menggunakan express (belum diuji tanpa tetapi saya curiga berfungsi). menggunakan simpul saat ini 0.10.22 dengan 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

Sebagian besar jawaban di sini menyarankan untuk menggunakan header req.headers.host.

Header Host diperlukan oleh HTTP 1.1, tetapi sebenarnya opsional karena header mungkin tidak benar-benar dikirim oleh klien HTTP, dan node / express akan menerima permintaan ini.

Anda mungkin bertanya: klien HTTP mana (mis: browser) yang dapat mengirim permintaan yang tidak memiliki tajuk itu? Protokol HTTP sangat sepele. Anda dapat membuat permintaan HTTP dalam beberapa baris kode, untuk tidak mengirim header host, dan jika setiap kali Anda menerima permintaan yang salah Anda melemparkan pengecualian, dan tergantung pada bagaimana Anda menangani pengecualian seperti itu, ini dapat menurunkan server Anda.

Jadi selalu validasikan semua input . Ini bukan paranoia, saya telah menerima permintaan yang kurang memiliki header host di layanan saya.

Juga, jangan pernah memperlakukan URL sebagai string . Gunakan modul url simpul untuk memodifikasi bagian-bagian tertentu dari string. Memperlakukan URL sebagai string dapat dieksploitasi dengan berbagai cara. Jangan lakukan itu.


Jawaban Anda akan sempurna jika Anda memberi contoh.
SerG

Kedengarannya sah tetapi beberapa referensi / tautan akan bagus.
Giwan

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

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

app.listen(80);

Ini adalah apa yang kami gunakan dan ini sangat bagus!


1
Jangan memperlakukan url sebagai string, gunakan modul url sebagai gantinya.
arboreal84

4
Berikan contoh dengan rasa tanggung jawab. Stack overflow adalah permainan telepon.
arboreal84

1
ini tidak akan menyebabkan loop tak terbatas?
Muhammad Umer

Tidak, karena hanya mendengarkan pada port 80 dan mengirim ke port 443. Satu-satunya cara loop tak terbatas akan terjadi adalah jika beberapa pendengar pada 443 mengalihkan kembali ke 80 yang tidak ada dalam kode saya. Saya harap itu masuk akal. Terima kasih!
Nick Kotenberg

3

Anda dapat menggunakan modul "net" untuk mendengarkan HTTP & HTTPS pada port yang sama

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
Ketika saya menjalankan ini pada Node 0.8, hanya server terakhir yang memanggil .listen yang sepertinya menjawab. Dalam hal ini, HTTP berfungsi, tetapi tidak HTTPS. Jika saya membalik urutan .createServer, maka HTTP berfungsi tetapi tidak HTTPS. :(
Joe

1
Ini tidak berfungsi seperti yang dijelaskan. Saya dapat mengkonfirmasi masalah yang dilihat Joe.
Stepan Mazurov

2

Ini bekerja untuk saya:

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

Ini mungkin benar, tetapi Anda harus menguraikan bagaimana ini menjawab pertanyaan si penanya.
Joe C


0

Ini bekerja untuk saya:

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

Rekomendasikan tambahkan header sebelum redirect ke https

Sekarang, ketika Anda melakukannya:

curl http://127.0.0.1 --include

Anda mendapatkan:

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/

Saya menggunakan express 4.17.1

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.