Автоматичне з'єднання HTTPS / переадресація за допомогою node.js / express


182

Я намагався налаштувати HTTPS з проектом node.js, над яким я працюю. Я по суті дотримувався документації node.js для цього прикладу:

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

Тепер, коли я

curl -k https://localhost:8000/

я отримав

hello world

як і очікувалося. Але якщо я

curl -k http://localhost:8000/

я отримав

curl: (52) Empty reply from server

З ретроспективою це здається очевидним, що це діяло б так, але в той же час люди, які врешті-решт відвідують мій проект, не збираються набирати https : // yadayada, і я хочу, щоб весь трафік був https з моменту їх потрапляння. сайт.

Як я можу отримати вузол (і Express, як це рамка, яку я використовую), щоб передати весь вхідний трафік до https, незалежно від того, вказано це чи ні? Я не зміг знайти жодної документації, яка б вирішила це. Або просто припускається, що у виробничому середовищі у вузла є щось, що сидить перед ним (наприклад, nginx), що обробляє такий вид перенаправлення?

Це мій перший набіг у розробці веб-сторінок, тому, будь ласка, пробачте моє незнання, якщо це щось очевидне.


Відповіли на цей лаконічно тут: stackoverflow.com/a/23894573/1882064
arcseldon

3
для тих, хто працює з ГЕРОКУ, відповіді на це запитання вам не допоможуть (ви отримаєте "занадто багато переадресацій"), але ця відповідь з іншого питання буде
Ріко Калер

Відповіді:


179

Райан, дякую за те, що я вказав мені в правильному напрямку. Я трохи деталізував вашу відповідь (другий абзац) з деяким кодом, і це працює. У цьому сценарії ці фрагменти коду розміщуються в моєму експрес-додатку:

// 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-сервер експресів слухає банкомат на 3000. Я встановив ці правила iptables, щоб вузол не запускався як 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

Все разом це працює саме так, як я цього хотів.


15
Дійсно важливе питання (щодо безпеки). Перед тим, як переадресація насправді відбудеться, чи може зловмисник нюхати та вкрасти файл cookie (ідентифікатор сесії)?
Коста

4
Як я можу виправити цей симптом? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
боді

16
Насправді це здається кращим , ... просто заверніть переспрямуванняif(!req.secure){}
bodine

6
Я просто хочу вказати відповідь на важливе питання @ Кости про безпеку наведені в іншому пості - stackoverflow.com/questions/8605720 / ...
ThisClark

2
можливо, захочете обернутися if(req.protocol==='http')заявою
Макс

120

Якщо ви дотримуєтесь звичайних портів, оскільки HTTP намагається порт 80 за замовчуванням, а HTTPS намагається порт 443 за замовчуванням, ви можете просто мати два сервери на одній машині: Ось код:

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:

$ curl https://127.0.0.1 -k
secure!

З 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

Детальніше: Nodejs HTTP та HTTPS через один і той же порт


4
res.writeHead(301, etc.)буде працювати правильно лише для дзвінків GET, оскільки 301не говорить клієнту використовувати той самий метод. Якщо ви хочете зберегти використаний метод (та всі інші параметри), який ви повинні використовувати res.writeHead(307, etc.). І якщо це все ще не працює, вам, можливо, доведеться зробити деякий проксі. Джерело: http://stackoverflow.com/a/17612942/1876359
ocramot

113

Дякую цьому хлопцю: 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
Це найкраща відповідь!
Стефан

3
Погоджена, має бути першою відповіддю.
1984.

12
Наступний рядок допоміг мені вирішити це рішення:app.enable('trust proxy');
arbel03

2
Як зазначено вище ^^^^: "довірений проксі" потрібен, якщо ви стоїте за будь-яким проксі, брандмауером або балансиром завантаження, який перенаправляє трафік: наприклад, балансування завантаження AWS, яке посилає всі запити http & https на той самий некореневий порт (наприклад, 3000) на веб-сервері VPC.
затишшя

1
для AWS ELB використовувати app.get('X-Forwarded-Proto') != 'http'замість req.secure aws.amazon.com/premiumsupport/knowledge-center / ...
Шак

25

За допомогою Nginx ви можете скористатися заголовком "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
Я знайшов req.headers ["x-forwarded-proto"] === "https") не надійним, проте req.secure працює!
Зугвалт

Я знайшов те саме, це видається дуже ненадійним.
дакопенгаген

2
req.secure - це правильний шлях, проте це баггі, якщо ви стоїте за проксі, тому що req.secure еквівалентний proto == "https", проте позаду проксі-експреса може сказати, що ваш прототип - https, http
dacopenhagen

7
Вам потрібно app.enable('trust proxy');: "Вказує, що програма стоїть за передньою стороною проксі, і використовувати заголовки X-Forwarded- * для визначення з'єднання та IP-адреси клієнта." expressjs.com/uk/4x/api.html#app.set
dskrvk

Не сприймайте URL-адреси як рядки, використовуйте натомість модуль url.
arboreal84

12

Станом на 0.4.12, у нас немає реального чистого способу прослуховування HTTP & HTTPS на одному порті за допомогою HTTP / HTTPS-серверів Node.

Деякі люди вирішили цю проблему, змусивши HTTPS-сервер Node (це працює і з Express.js) прослуховувати 443 (або якийсь інший порт), а також невеликий http-сервер, який прив’язується до 80 і перенаправляє користувачів до захищеного порту.

Якщо ви абсолютно повинні мати можливість обробляти обидва протоколи на одному порті, тоді вам потрібно поставити nginx, lighttpd, apache або якийсь інший веб-сервер на цьому порту і діяти як зворотний проксі для Node.


Дякую Райан До «... є невеликий HTTP - сервер зв'язуються з 80 і редиректу ...» Я припускаю , що ви маєте в виду інший вузол сервера HTTP. Я думаю, я маю уявлення про те, як це може працювати, але чи можете ви вказати на будь-який приклад код? Крім того, чи знаєте ви, чи є "чистішим" рішенням цього питання десь у дорожній карті вузла?
Джейк

Ваша програма Node.js може мати декілька серверів http (s). Я перевірив проблеми Node.js ( github.com/joyent/node/isissue ) та список розсилки ( groups.google.com/group/nodejs ) і не побачив жодних повідомлень про проблеми, але побачив пару публікацій про проблему у списку розсилки. Наскільки я можу сказати, це не в трубі. Я рекомендую повідомити про це на Github і подивитися, який відгук ви отримаєте.
Райан Олдс

Дійсно важливе питання (щодо безпеки). Перед тим, як переадресація насправді відбудеться, чи може "зловмисник" обнюхати та вкрасти файл cookie (ідентифікатор сесії)?
Коста,

Так, код статусу 3xx та, можливо, заголовок Місцеположення повертаються до агента, після чого агент запитує URL-адресу, вказану заголовком Location.
Райан Олдс

Це 2015 рік, це все-таки так?
Екологів,

10

Ви можете використовувати модуль 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
Використовуйте це з обережністю, оскільки пакет не оновлювався роками. Я зіткнувся з проблемами в AWS, де кодування було поганим.
Мартавіс П.

1
пакет сестри: npmjs.com/package/express-to-https - але я не маю уявлення, чи працює це / isbetter / тощо
quetzalcoatl

Зауважте, що якщо ви використовуєте проміжне програмне забезпечення для подання статичних файлів (включаючи односторінкові програми), будь-яке посереднє програмне забезпечення для переадресації повинно бути приєднане appспочатку. (див. цю відповідь )
Метью Р.

9

Я використовую рішення, запропоноване Basarat, але мені також потрібно перезаписати порт, тому що я мав 2 різні порти для протоколів HTTP та HTTPS.

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

Я вважаю за краще використовувати не стандартний порт, щоб запустити nodejs без привілеїв root. Мені подобаються 8080 та 8443, тому що я прийшов з багатьох років програмування на tomcat.

Моїм повним файлом стають

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

Тоді я використовую iptable для forwording 80 та 443 трафіку на моїх портах HTTP та 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

Дякую Лоренцо, це працювало для мене. Замість використання опцій я використав letsencrypt. Я видалив варіанти варіантів. Я додав параметри letsencrypt (privateKey, сертифікат, ca і облікові дані), і я змінив параметри на облікові дані: https.createServer (облікові дані, додаток)
Nhon Ha

8

Цю відповідь потрібно оновити для роботи з Express 4.0. Ось як я змусив працювати окремий сервер 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
Я змусив це працювати, змінивши httpApp.use ('*', httpRouter); до httpApp.use ('/', httpRouter); і перемістити його до рядка перед створенням hhtp-сервера.
KungWaz

8

Якщо ваша програма стоїть за надійним проксі-сервером (наприклад, AWS ELB або правильно налаштованою nginx), цей код повинен працювати:

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

Примітки:

  • Це передбачає, що ви розміщуєте свій веб-сайт на 80 і 443, якщо ні, вам потрібно буде змінити порт при перенаправлення
  • Це також передбачає, що ви припиняєте SSL на проксі. Якщо ви робите SSL для кінця, скористайтеся відповіддю від @basarat вище. Краще до кінця SSL - краще рішення.
  • app.enable ('довірений проксі') дозволяє експресу перевірити заголовок X-Forwarded-Proto

6

Я знаходжу, що req.protocol працює, коли я використовую експрес (не тестував без, але я підозрюю, що це працює). використовуючи поточний вузол 0.10.22 з виразом 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

Більшість відповідей тут пропонує використовувати заголовок req.headers.host.

Заголовок хоста вимагає HTTP 1.1, але він фактично необов’язковий, оскільки заголовок може бути фактично не надісланий клієнтом HTTP, і вузол / експрес прийме цей запит.

Ви можете запитати: який HTTP-клієнт (наприклад: браузер) може надіслати запит про відсутність цього заголовка? Протокол HTTP дуже тривіальний. Ви можете створити HTTP-запит у кількох рядках коду, щоб не надсилати заголовок хоста, і якщо кожен раз, коли ви отримуєте запит із неправильним формуванням, ви кидаєте виняток, і залежно від того, як ви обробляєте такі винятки, це може зняти ваш сервер.

Тому завжди підтверджуйте всі дані . Це не параноя, я отримував запити, у яких не вистачає заголовка хоста в моїй службі.

Крім того, ніколи не трактуйте URL-адреси як рядки . Використовуйте модуль URL-вузла для зміни конкретних частин рядка. Трактування URL-адрес як рядків може використовуватися багатьма багатьма способами. Не робіть цього.


Ваша відповідь була б ідеальною, якби ви подали приклад.
СерГ

Звучить законно, але деякі посилання / посилання були б чудовими.
Giwan

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

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

app.listen(80);

Це те, що ми використовуємо, і це чудово працює!


1
Не сприймайте URL-адреси як рядки, використовуйте натомість модуль url.
arboreal84

4
Наведіть приклади з почуттям відповідальності. Переповнення стека - це телефонна гра.
arboreal84

1
це не викликає нескінченну петлю?
Мухаммед Умер

Ні, тому що він прослуховує тільки порт 80 і надсилає до порту 443. Єдиний спосіб, коли трапиться нескінченний цикл, - це якщо якийсь слухач на 443 перенаправить назад на 80, що не в моєму коді. Сподіваюся, це має сенс. Дякую!
Нік Котенберг

3

ви можете використовувати модуль "net" для прослуховування HTTP та HTTPS на одному порті

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
Коли я запускаю це на Node 0.8, здається, відповідає лише останній сервер, який викликає .listen. У цьому випадку HTTP працює, але не HTTPS. Якщо я відміняю порядок .createServer, тоді HTTP працює, але не HTTPS. :(
Джо

1
Це не працює, як описано. Я можу підтвердити проблему, яку Джо побачив.
Степан Мазуров

2

Це працювало для мене:

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

Це може бути правильно, але вам слід детальніше пояснити, як це відповідає на запитання запитувача.
Джо C

1

Ви можете встановити два сервери Node.js - один для HTTP та HTTPS

Ви також можете визначити функцію налаштування, яку будуть виконувати обидва сервери, так що вам не доведеться писати багато дублювання коду.

Ось так, як я це зробив: (використовуючи restify.js, але повинен працювати для express.js або також для себе вузла)

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


0

Це працювало для мене:

/* 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

Тепер, коли ви робите:

curl http://127.0.0.1 --include

Ви отримуєте:

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/

Я використовую експрес 4.17.1

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.