Conexão / redirecionamento HTTPS automático com node.js / express

181

Eu tenho tentado configurar o HTTPS com um projeto node.js no qual estou trabalhando. Segui essencialmente a documentação do node.js. para este exemplo:

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

Agora, quando eu faço

curl -k https://localhost:8000/

eu recebo

hello world

como esperado. Mas se eu fizer

curl -k http://localhost:8000/

eu recebo

curl: (52) Empty reply from server

Em retrospecto, parece óbvio que funcionaria dessa maneira, mas, ao mesmo tempo, as pessoas que eventualmente visitarem meu projeto não digitarão https : // yadayada, e eu quero que todo o tráfego seja https a partir do momento em que eles acessarem o site.

Como posso obter o nó (e o Express, como essa é a estrutura que estou usando) para entregar todo o tráfego de entrada para https, independentemente de ter sido especificado ou não? Não consegui encontrar nenhuma documentação que tratasse disso. Ou apenas assume-se que, em um ambiente de produção, o nó tem algo à sua frente (por exemplo, nginx) que lida com esse tipo de redirecionamento?

Esta é a minha primeira incursão no desenvolvimento web, então, perdoe minha ignorância se isso for algo óbvio.

Jake
fonte
Responderam a esta sucintamente aqui: stackoverflow.com/a/23894573/1882064
arcseldon
3
para qualquer um implantando em Heroku, as respostas a esta pergunta não irá ajudá-lo (você vai ter "muitos redirecionamentos"), mas esta resposta a partir de uma outra questão vai
Rico Kahler

Respostas:

179

Ryan, obrigado por me apontar na direção certa. Eu ampliei sua resposta (segundo parágrafo) um pouco com algum código e ele funciona. Nesse cenário, esses trechos de código são colocados no meu aplicativo expresso:

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

O servidor https express escuta o ATM em 3000. Eu configurei essas regras do iptables para que o nó não precise ser executado como 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

Todos juntos, isso funciona exatamente como eu queria.

Jake
fonte
15
Pergunta realmente importante (em relação à segurança). Antes que o redirecionamento realmente aconteça, é possível que um invasor cheire e roube um cookie (ID da sessão)?
Costa
4
Como eu consertaria esse sintoma resultante? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
bodine 27/03
16
Na verdade, isso parece melhor , ... basta envolver o redirecionamento comif(!req.secure){}
bodine
6
Eu só quero apontar a resposta para a importante pergunta de @ Costa sobre segurança, dada em outro post - stackoverflow.com/questions/8605720/…
ThisClark
2
pode querer envolver em torno de uma if(req.protocol==='http')declaração
Max
120

Se você seguir as portas convencionais, já que o HTTP tenta a porta 80 por padrão e o HTTPS tenta a porta 443 por padrão, você pode simplesmente ter dois servidores na mesma máquina: Aqui está o código:

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

Teste com https:

$ curl https://127.0.0.1 -k
secure!

Com 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

Mais detalhes: Nodejs HTTP e HTTPS na mesma porta

basarat
fonte
4
res.writeHead(301, etc.)funcionará corretamente apenas para chamadas GET, pois 301não está dizendo ao cliente para usar o mesmo método. Se você quiser manter o método usado (e todos os outros parâmetros), precisará usar res.writeHead(307, etc.). E se ainda não funcionar, você pode precisar fazer proxy. Fonte: http://stackoverflow.com/a/17612942/1876359
ocramot 29/17/17
113

Graças a esse cara: 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);
        }
});
Jeremias Binder
fonte
11
Esta é a melhor resposta!
Steffan
3
Concordado, deve ser a primeira resposta.
1984
12
A linha a seguir ajudaram neste trabalho solução para mim:app.enable('trust proxy');
arbel03
2
Conforme declarado acima ^^^^: 'proxy de confiança' é necessário se você estiver atrás de qualquer tipo de proxy, firewall ou balanceador de carga que redirecione o tráfego: por exemplo, balanceamento de carga da AWS que envia todas as solicitações http e https para a mesma porta não raiz (por exemplo, 3000) no servidor da web da sua VPC.
precisa saber é o seguinte
1
para AWS ELB usar app.get('X-Forwarded-Proto') != 'http'em vez de req.secure aws.amazon.com/premiumsupport/knowledge-center/...
shak
25

Com o Nginx, você pode aproveitar o cabeçalho "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);  
}
ccyrille
fonte
5
Eu achei req.headers ["x-forwarded-proto"] === "https") não confiável, no entanto req.secure funciona!
Zugwalt
Achei o mesmo, isso parece muito confiável.
Dacopenhagen
2
req.secure é a maneira correta, no entanto, isso é um buggy se você estiver atrás de um proxy, porque req.secure é equivalente a proto == "https", mas atrás de um proxy express pode dizer que seu proto é https, http
dacopenhagen
7
Você precisa app.enable('trust proxy');: "Indica que o aplicativo está protegido por um proxy frontal e usar os cabeçalhos X-Forwarded- * para determinar a conexão e o endereço IP do cliente." expressjs.com/en/4x/api.html#app.set
dskrvk
Não trate URLs como strings, use o módulo URL.
Arboreal84
12

A partir da versão 0.4.12, não havia uma maneira clara de escutar HTTP e HTTPS na mesma porta, usando os servidores HTTP / HTTPS do Node.

Algumas pessoas resolveram esse problema fazendo com que o servidor HTTPS do Node (que também funciona com o Express.js) ouça 443 (ou alguma outra porta) e também tenha um pequeno servidor http vinculado a 80 e redirecione os usuários para a porta segura.

Se você precisar absolutamente manipular os dois protocolos em uma única porta, precisará colocar nginx, lighttpd, apache ou algum outro servidor Web nessa porta e agir como um proxy reverso para o Node.

Ryan Olds
fonte
Obrigado Ryan. Por "... tenha um pequeno servidor http vinculado a 80 e redirecione ..." Suponho que você queira dizer outro servidor http do . Eu acho que tenho uma idéia de como isso pode funcionar, mas você pode apontar para qualquer código de exemplo? Além disso, você sabe se uma solução "limpa" para esse problema está em algum lugar no roteiro do nó?
Jake
Seu aplicativo Node.js. pode ter vários servidores http (s). Verifiquei os problemas do Node.js. ( github.com/joyent/node/issues ) e a lista de discussão ( groups.google.com/group/nodejs ) e não vi nenhum problema relatado, mas vi algumas postagens sobre o problema na lista de discussão. Até onde eu sei, isso não está no cachimbo. Eu recomendaria denunciá-lo no github e ver qual feedback você recebe.
Ryan Olds
Pergunta realmente importante (em relação à segurança). Antes que o redirecionamento realmente aconteça, é possível que um "invasor" fareje e roube um cookie (ID da sessão)?
Costa
Sim, um código de status 3xx e, possivelmente, um cabeçalho de localização são enviados de volta ao agente; nesse momento, o agente solicita a URL especificada pelo cabeçalho de localização.
Ryan Olds
É 2015, esse ainda é o caso?
TheEnvironmentalist
10

Você pode usar o módulo 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);
Mateus Ferreira
fonte
1
Use isso com cuidado, pois o pacote não foi atualizado há anos. Encontrei problemas na AWS em que a codificação era ruim.
Martavis P.
1
pacote irmã: npmjs.com/package/express-to-https - mas eu não tenho idéia se ele funciona / isbetter / etc
quetzalcoatl
Observe que, se você estiver usando o middleware para veicular arquivos estáticos (incluindo aplicativos de página única), qualquer middleware de redirecionamento deverá ser anexado appprimeiro. (veja esta resposta )
Matthew R.
9

Uso a solução proposta pela Basarat, mas também preciso sobrescrever a porta, pois costumava ter 2 portas diferentes para os protocolos HTTP e HTTPS.

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

Também prefiro usar porta não padrão, para iniciar o nodejs sem privilégios de root. Eu gosto de 8080 e 8443 porque vim de muitos anos de programação no tomcat.

Meu arquivo completo se torna

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

Então eu uso o iptable para alterar o tráfego 80 e 443 nas minhas portas HTTP e 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
Lorenzo Eccher
fonte
Obrigado Lorenzo, isso funcionou para mim. Em vez de usar opções, usei letsencrypt. Eu apaguei as opções var. Eu adicionei os parâmetros letsencrypt (PrivateKey, certificado, Ca e credenciais), e eu mudei opções para as credenciais: https.createServer (credenciais, app)
Nhon Ha
8

Esta resposta precisa ser atualizada para funcionar com o Express 4.0. Aqui está como eu consegui o servidor http separado para trabalhar:

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);
Acéfalo
fonte
1
Consegui que isso funcionasse alterando httpApp.use ('*', httpRouter); para httpApp.use ('/', httpRouter); e movê-lo para a linha antes de criar o servidor hhtp.
KungWaz
8

Se seu aplicativo estiver protegido por um proxy confiável (por exemplo, um AWS ELB ou um nginx configurado corretamente), este código funcionará:

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

Notas:

  • Isso pressupõe que você esteja hospedando seu site nas versões 80 e 443; caso contrário, será necessário alterar a porta ao redirecionar
  • Isso também pressupõe que você esteja encerrando o SSL no proxy. Se você usa SSL de ponta a ponta, use a resposta de @basarat acima. O SSL de ponta a ponta é a melhor solução.
  • app.enable ('proxy de confiança') permite que express verifique o cabeçalho X-Forwarded-Proto
Jamie McCrindle
fonte
6

Acho req.protocol funciona quando estou usando express (não testei sem, mas suspeito que funcione). usando o nó atual 0.10.22 com 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();
  } 
});
Catalisador
fonte
5

A maioria das respostas aqui sugere o uso do cabeçalho req.headers.host.

O cabeçalho do host é exigido pelo HTTP 1.1, mas é realmente opcional, pois o cabeçalho pode não ser realmente enviado por um cliente HTTP, e o node / express aceitará essa solicitação.

Você pode perguntar: qual cliente HTTP (por exemplo: navegador) pode enviar uma solicitação sem esse cabeçalho? O protocolo HTTP é muito trivial. Você pode criar uma solicitação HTTP em poucas linhas de código, para não enviar um cabeçalho de host e, sempre que receber uma solicitação malformada, você lançará uma exceção e, dependendo de como você lida com essas exceções, isso pode levar o seu servidor para baixo.

Portanto, sempre valide toda a entrada . Isso não é paranóia, recebi solicitações sem o cabeçalho do host no meu serviço.

Além disso, nunca trate URLs como strings . Use o módulo de URL do nó para modificar partes específicas de uma sequência. O tratamento de URLs como seqüências de caracteres pode ser explorado de várias maneiras. Não faça isso.

arboreal84
fonte
Sua resposta seria perfeita se você desse um exemplo.
SerG 27/10
Parece legítimo, mas algumas referências / links seriam ótimos.
Giwan 5/06/19
3
var express = require('express');
var app = express();

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

app.listen(80);

É isso que usamos e funciona muito bem!

Nick Kotenberg
fonte
1
Não trate URLs como strings, use o módulo URL.
Arboreal84
4
Dê exemplos com um senso de responsabilidade. O estouro de pilha é o jogo telefônico.
arboreal84
1
isso não causará loop infinito?
Muhammad Umer
Não, porque apenas escuta na porta 80 e envia para a porta 443. A única maneira de ocorrer um loop infinito é se algum ouvinte no 443 redirecionar de volta para 80, o que não está no meu código. Espero que faça sentido. Obrigado!
precisa saber é o seguinte
3

você pode usar o módulo "net" para ouvir HTTP e HTTPS na mesma porta

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)
luofei614
fonte
5
Quando executo isso no nó 0.8, apenas o último servidor que chama .listen parece responder. Nesse caso, o HTTP funciona, mas não o HTTPS. Se eu inverter a ordem de .createServer, o HTTP funcionará, mas não o HTTPS. :(
Joe
1
Isso não funciona como descrito. Posso confirmar o problema que Joe viu.
Stepan Mazurov
2

Isso funcionou para mim:

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();
    } 
});
Shummy1991
fonte
Isso pode estar certo, mas você deve explicar como isso responde à pergunta do solicitante.
Joe C
1

Você pode instanciar 2 servidores Node.js - um para HTTP e HTTPS

Você também pode definir uma função de configuração que os dois servidores executarão, para que você não precise escrever muito código duplicado.

Aqui está a maneira como eu fiz isso: (usando restify.js, mas deve funcionar para express.js, ou o próprio nó também)

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

aguardar
fonte
0

Isso funcionou para mim:

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

Recomende adicionar os cabeçalhos antes de redirecionar para https

Agora, quando você faz:

curl http://127.0.0.1 --include

Você obtém:

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/

Eu uso express 4.17.1

χρηστος απατσιδης
fonte