Kết nối / chuyển hướng HTTPS tự động với node.js / express


181

Tôi đã cố gắng để thiết lập HTTPS với dự án node.js mà tôi đang làm việc. Về cơ bản, tôi đã làm theo tài liệu của node.js cho ví dụ này:

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

Bây giờ, khi tôi làm

curl -k https://localhost:8000/

tôi có

hello world

như mong đợi. Nhưng nếu tôi làm

curl -k http://localhost:8000/

tôi có

curl: (52) Empty reply from server

Nhìn lại, điều này có vẻ hiển nhiên là nó sẽ hoạt động theo cách này, nhưng đồng thời, những người cuối cùng ghé thăm dự án của tôi sẽ không nhập https : // yadayada và tôi muốn tất cả lưu lượng truy cập là https từ thời điểm họ truy cập trang web.

Làm cách nào tôi có thể nhận nút (và Express vì đó là khung tôi đang sử dụng) để chuyển tất cả lưu lượng truy cập đến https, bất kể nó có được chỉ định hay không? Tôi đã không thể tìm thấy bất kỳ tài liệu đã giải quyết điều này. Hoặc nó chỉ giả định rằng trong một môi trường sản xuất, nút có một cái gì đó nằm trước nó (ví dụ nginx) xử lý loại chuyển hướng này?

Đây là bước đột phá đầu tiên của tôi vào phát triển web, vì vậy xin vui lòng tha thứ cho sự thiếu hiểu biết của tôi nếu đây là một điều hiển nhiên.


Đã trả lời ngắn gọn ở đây: stackoverflow.com/a/23894573/1882064
arcseldon

3
đối với bất kỳ ai gửi tới HEROKU, các câu trả lời trong câu hỏi này sẽ không giúp bạn (bạn sẽ nhận được "quá nhiều chuyển hướng") nhưng câu trả lời này từ một câu hỏi khác sẽ
Rico Kahler

Câu trả lời:


179

Ryan, cảm ơn vì đã chỉ cho tôi đi đúng hướng. Tôi đã bổ sung câu trả lời của bạn (đoạn 2) một chút với một số mã và nó hoạt động. Trong trường hợp này, các đoạn mã được đặt trong ứng dụng thể hiện của tôi:

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

Máy chủ https express lắng nghe ATM trên 3000. Tôi thiết lập các quy tắc iptables này để nút không phải chạy dưới quyền 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

Tất cả cùng nhau, điều này hoạt động chính xác như tôi muốn nó.


15
Câu hỏi thực sự quan trọng (liên quan đến bảo mật). Trước khi chuyển hướng đó thực sự xảy ra, kẻ tấn công có thể đánh hơi và đánh cắp cookie (ID phiên) không?
Costa

4
Làm thế nào tôi có thể khắc phục triệu chứng này? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
cơ thể

16
Trên thực tế, điều này có vẻ tốt hơn , ... chỉ cần bọc chuyển hướng vớiif(!req.secure){}
cơ thể

6
Tôi chỉ muốn chỉ ra câu trả lời cho câu hỏi quan trọng của @ Costa về bảo mật được đưa ra trong một bài đăng khác - stackoverflow.com/questions/8605720/
Kẻ

2
có thể muốn bao quanh một if(req.protocol==='http')tuyên bố
Tối đa

120

Nếu bạn theo các cổng thông thường vì HTTP cố gắng cổng 80 theo mặc định và HTTPS mặc định thử cổng 443, bạn chỉ cần có hai máy chủ trên cùng một máy: Đây là mã:

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

Kiểm tra bằng https:

$ curl https://127.0.0.1 -k
secure!

Với 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

Thêm chi tiết: Nodejs HTTP và HTTPS trên cùng một cổng


4
res.writeHead(301, etc.)sẽ chỉ hoạt động chính xác cho các cuộc gọi GET, vì 301không nói cho khách hàng sử dụng cùng một phương thức. Nếu bạn muốn giữ phương thức được sử dụng (và tất cả các tham số khác), bạn phải sử dụng res.writeHead(307, etc.). Và nếu nó vẫn không hoạt động, bạn có thể phải thực hiện một số ủy quyền. Nguồn: http://stackoverflow.com/a/17612942/1876359
ocramot

113

Cảm ơn anh chàng này: 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
Đây là câu trả lời tốt nhất!
Steffan

3
Đồng ý, nên là câu trả lời đầu tiên.
1984

12
Dòng sau đây đã giúp giải pháp này hiệu quả với tôi:app.enable('trust proxy');
arbel03

2
Như đã nêu ở trên ^^ ^ ^ ^: 'proxy proxy' là bắt buộc nếu bạn đứng sau bất kỳ loại proxy, tường lửa hoặc bộ cân bằng tải nào chuyển hướng lưu lượng truy cập: ví dụ: cân bằng tải AWS gửi tất cả các yêu cầu http & https đến cùng một cổng không phải root (ví dụ: 3000) trên máy chủ web của VPC của bạn.
alanwaring

1
cho AWS ELB sử dụng app.get('X-Forwarded-Proto') != 'http'thay vì req.secure aws.amazon.com/premiumsupport/knowledge-center/...
shak

25

Với Nginx, bạn có thể tận dụng tiêu đề "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
Tôi thấy req.headers ["x-Forwarded-proto"] === "https") không đáng tin cậy, tuy nhiên req.secure hoạt động!
Zugwalt

Tôi thấy như vậy, điều này xuất hiện rất không đáng tin cậy.
dacopenhagen

2
req.secure là cách thích hợp, tuy nhiên đây là lỗi nếu bạn đứng sau proxy vì req.secure tương đương với proto == "https", tuy nhiên đằng sau một proxy thể hiện có thể nói proto của bạn là https, http
dacopenhagen

7
Bạn cần app.enable('trust proxy');: "Cho biết ứng dụng nằm phía sau proxy phía trước và sử dụng các tiêu đề X-Forwarded- * để xác định kết nối và địa chỉ IP của máy khách." expressjs.com/en/4x/api.html#app.set
dskrvk

Đừng coi url là chuỗi, thay vào đó hãy sử dụng mô-đun url.
arboreal84

12

Kể từ ngày 0.4.12, chúng tôi không có cách nào thực sự lắng nghe HTTP & HTTPS trên cùng một cổng bằng máy chủ HTTP / HTTPS của Node.

Một số người đã giải quyết vấn đề này bằng cách có máy chủ HTTPS của Node (điều này cũng hoạt động với Express.js) lắng nghe 443 (hoặc một số cổng khác) và cũng có một máy chủ http nhỏ liên kết với 80 và chuyển hướng người dùng đến cổng an toàn.

Nếu bạn hoàn toàn phải có khả năng xử lý cả hai giao thức trên một cổng thì bạn cần đặt nginx, lighttpd, apache hoặc một số máy chủ web khác trên cổng đó và hoạt động như một proxy ngược cho Node.


Cảm ơn Ryan. Bởi "... có một máy chủ http nhỏ liên kết với 80 và chuyển hướng ..." Tôi giả sử bạn có nghĩa là một máy chủ http nút khác . Tôi nghĩ rằng tôi có một ý tưởng về cách thức này có thể hoạt động nhưng bạn có thể chỉ ra bất kỳ mã ví dụ nào không? Ngoài ra, bạn có biết liệu một giải pháp "sạch" hơn cho vấn đề này có ở bất kỳ đâu trong lộ trình nút không?
Jake

Ứng dụng Node.js của bạn có thể có nhiều máy chủ http (s). Tôi đã kiểm tra các sự cố Node.js ( github.com/joyent/node/issues ) và danh sách gửi thư ( Groups.google.com/group/nodejs ) và không thấy bất kỳ vấn đề nào được báo cáo, nhưng đã thấy một số bài đăng về vấn đề này trên danh sách gửi thư. Theo như tôi có thể nói điều này không có trong đường ống. Tôi sẽ khuyên bạn nên báo cáo nó trên github và xem phản hồi bạn nhận được.
Ryan Olds

Câu hỏi thực sự quan trọng (liên quan đến bảo mật). Trước khi chuyển hướng đó thực sự xảy ra, liệu "kẻ tấn công" có thể phát hiện ra và đánh cắp cookie (ID phiên) không?
Costa

Có, mã trạng thái 3xx và có thể là tiêu đề Vị trí được gửi lại cho tác nhân, tại đó, tác nhân yêu cầu URL được chỉ định bởi tiêu đề Vị trí.
Ryan Olds

Đó là năm 2015, đây vẫn là trường hợp?
TheEnvironmentalist

10

Bạn có thể sử dụng mô-đun 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
Sử dụng cẩn thận vì gói này chưa được cập nhật trong nhiều năm. Tôi gặp vấn đề trong AWS nơi mã hóa là xấu.
Martavis P.

1
gói chị em: npmjs.com/package/express-to-https - nhưng tôi không biết nó có hoạt động / isbetter / etc
quetzalcoatl

Lưu ý rằng nếu bạn đang sử dụng phần mềm trung gian để phân phát các tệp tĩnh (bao gồm các ứng dụng một trang), thì bất kỳ phần mềm trung gian chuyển hướng nào cũng phải được đính kèm apptrước. (xem câu trả lời này )
Matthew R.

9

Tôi sử dụng giải pháp do Basarat đề xuất nhưng tôi cũng cần ghi đè lên cổng vì tôi đã từng có 2 cổng khác nhau cho giao thức HTTP và HTTPS.

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

Tôi cũng thích sử dụng cổng không chuẩn để bắt đầu nodejs mà không có quyền root. Tôi thích 8080 và 8443 vì tôi đã có rất nhiều năm lập trình trên tomcat.

Tập tin hoàn chỉnh của tôi trở thành

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

Sau đó, tôi sử dụng iptable để báo trước lưu lượng 80 và 443 trên các cổng HTTP và HTTPS của mình.

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

Cảm ơn Lorenzo, điều này đã làm việc cho tôi. Thay vì sử dụng các tùy chọn, tôi đã sử dụng letencrypt. Tôi đã xóa tùy chọn var. Tôi đã thêm các tham số letencrypt (privateKey, chứng chỉ, ca và thông tin đăng nhập) và tôi đã thay đổi tùy chọn thành thông tin đăng nhập: https.createServer (thông tin đăng nhập, ứng dụng)
Ha

8

Câu trả lời này cần được cập nhật để hoạt động với Express 4.0. Đây là cách tôi có máy chủ http riêng để hoạt động:

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
Tôi đã làm điều này để hoạt động bằng cách thay đổi httpApp.use ('*', httpRouter); đến httpApp.use ('/', httpRouter); và di chuyển nó đến dòng trước khi bạn tạo máy chủ hhtp.
KungWaz

8

Nếu ứng dụng của bạn đứng sau proxy đáng tin cậy (ví dụ ELS ELB hoặc nginx được định cấu hình chính xác), mã này sẽ hoạt động:

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

Ghi chú:

  • Điều này giả định rằng bạn đang lưu trữ trang web của mình trên 80 và 443, nếu không, bạn sẽ cần thay đổi cổng khi bạn chuyển hướng
  • Điều này cũng giả sử rằng bạn đang chấm dứt SSL trên proxy. Nếu bạn đang làm SSL từ đầu đến cuối, hãy sử dụng câu trả lời từ @basarat ở trên. Kết thúc để kết thúc SSL là giải pháp tốt hơn.
  • app.enable ('proxy proxy') cho phép express để kiểm tra tiêu đề X-Forwarded-Proto

6

Tôi thấy req.protatio hoạt động khi tôi đang sử dụng express (chưa được kiểm tra mà không có nhưng tôi nghi ngờ nó hoạt động). sử dụng nút hiện tại 0.10.22 với 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

Hầu hết các câu trả lời ở đây đề nghị sử dụng tiêu đề req.headers.host.

Tiêu đề máy chủ được HTTP 1.1 yêu cầu, nhưng thực tế nó là tùy chọn vì tiêu đề có thể không thực sự được gửi bởi máy khách HTTP và nút / express sẽ chấp nhận yêu cầu này.

Bạn có thể hỏi: máy khách HTTP nào (ví dụ: trình duyệt) có thể gửi yêu cầu thiếu tiêu đề đó không? Giao thức HTTP rất tầm thường. Bạn có thể tạo một yêu cầu HTTP trong một vài dòng mã, để không gửi tiêu đề máy chủ và nếu mỗi lần bạn nhận được yêu cầu không đúng định dạng, bạn sẽ đưa ra một ngoại lệ và tùy thuộc vào cách bạn xử lý các ngoại lệ đó, điều này có thể làm hỏng máy chủ của bạn.

Vì vậy, luôn luôn xác nhận tất cả các đầu vào . Đây không phải là hoang tưởng, tôi đã nhận được yêu cầu thiếu tiêu đề máy chủ trong dịch vụ của mình.

Ngoài ra, không bao giờ coi URL là chuỗi . Sử dụng mô-đun url nút để sửa đổi các phần cụ thể của chuỗi. Xử lý URL dưới dạng chuỗi có thể được khai thác theo nhiều cách. Đừng làm điều đó.


Câu trả lời của bạn sẽ hoàn hảo nếu bạn đã đưa ra một ví dụ.
SerG

Âm thanh hợp pháp nhưng một số tài liệu tham khảo / liên kết sẽ là tuyệt vời.
Giwan

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

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

app.listen(80);

Đây là những gì chúng tôi sử dụng và nó hoạt động tuyệt vời!


1
Đừng coi url là chuỗi, thay vào đó hãy sử dụng mô-đun url.
arboreal84

4
Cho ví dụ với tinh thần trách nhiệm. Tràn ngăn xếp là trò chơi điện thoại.
arboreal84

1
Điều này sẽ không gây ra vòng lặp vô hạn?
Muhammad Umer

Không, bởi vì nó chỉ lắng nghe trên cổng 80 và gửi đến cổng 443. Cách duy nhất một vòng lặp vô hạn sẽ xảy ra là nếu một số người nghe trên 443 chuyển hướng trở lại 80 không có trong mã của tôi. Tôi hy vọng điều đó đúng. Cảm ơn!
Nick Kotenberg

3

bạn có thể sử dụng mô-đun "net" để nghe HTTP & HTTPS trên cùng một cổng

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
Khi tôi chạy cái này trên Node 0.8, chỉ có máy chủ cuối cùng gọi .listen dường như trả lời. Trong trường hợp này, HTTP hoạt động, nhưng không phải HTTPS. Nếu tôi đảo ngược thứ tự của .createServer, thì HTTP hoạt động nhưng không hoạt động HTTPS. :(
Joe

1
Điều này không hoạt động như mô tả. Tôi có thể xác nhận vấn đề Joe thấy.
Stepan Mazurov

2

Điều này làm việc cho tôi:

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

Điều này có thể đúng, nhưng bạn nên giải thích cách trả lời câu hỏi của người hỏi.
Joe C


0

Điều này làm việc cho tôi:

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

Đề xuất thêm các tiêu đề trước khi chuyển hướng đến https

Bây giờ, khi bạn làm:

curl http://127.0.0.1 --include

Bạn lấy:

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/

Tôi sử dụng express 4.17.1

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.