web-dev-qa-db-pt.com

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

Eu tenho tentado obter o HTTPS configurado com um projeto node.js no qual estou trabalhando. Eu essencialmente segui o 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, isso parece óbvio que funcionaria dessa maneira, mas, ao mesmo tempo, as pessoas que eventualmente visitam meu projeto não vão digitar https : // yadayada, e quero que todo o tráfego seja https a partir do momento em que eles acessam o site.

Como posso obter o nó (e Express, como é o framework que estou usando) para distribuir todo o tráfego de entrada para https, independentemente de ter sido especificado ou não? Não consegui encontrar nenhuma documentação que tenha resolvido isso. Ou é apenas suposto que em um ambiente de produção, o nó tem algo que fica na frente dele (por exemplo, nginx) que lida com esse tipo de redirecionamento?

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

145
Jake

Ryan, obrigado por me apontar na direção certa. Eu preparei sua resposta (segundo parágrafo) um pouco com algum código e funciona. Neste 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 configuro 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

Tudo junto, isso funciona exatamente como eu queria.

149
Jake

Se você seguir as portas convencionais desde que o HTTP tente a porta 80 por padrão e HTTPS tente 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 sobre a mesma porta

105
basarat

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);
        }
});
57
Jeremias Binder

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);  
}
21
ccyrille

A partir de 0.4.12, não temos uma maneira real e limpa de escutar HTTP & HTTPS na mesma porta usando os servidores HTTP/HTTPS do Node.

Algumas pessoas resolveram esse problema tendo o servidor HTTPS do Node (isso também funciona com o Express.js), ouça o 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ê realmente tem que ser capaz de lidar com ambos os protocolos em uma única porta, então você precisa colocar o nginx, o lighttpd, o Apache ou algum outro servidor web naquela porta e agir como um proxy reverso para o Node.

12
Ryan Olds

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

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

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

Se seu aplicativo estiver por trás de um proxy confiável (por exemplo, um AWS ELB ou um nginx configurado corretamente), esse código deve 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 em 80 e 443, caso contrário, você precisará alterar a porta quando redirecionar
  • Isso também pressupõe que você está finalizando o SSL no proxy. Se você estiver usando SSL de ponta a ponta, use a resposta de @basarat acima. De ponta a ponta, o SSL é a melhor solução.
  • app.enable ('proxy de confiança') permite expressar para verificar o cabeçalho X-Forwarded-Proto
5
Jamie McCrindle

A maioria das respostas aqui sugere usar o cabeçalho req.headers.Host.

O cabeçalho do Host é requerido pelo HTTP 1.1, mas é na verdade opcional já que o cabeçalho pode não ser realmente enviado por um cliente HTTP e o nó/expresso aceitará este pedido.

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, se cada vez que receber uma solicitação malformada, você lançar uma exceção e, dependendo de como lidar com essas exceções, isso pode derrubar seu servidor.

Então sempre valida toda entrada. Isso não é paranoia, recebi solicitações sem o cabeçalho 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 string. Tratar URLs como strings pode ser explorado de muitas maneiras. Não faça isso.

4
arboreal84

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);
4
Mateus Dal Bianco
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!

3
Nick Kotenberg

você pode usar o módulo "net" para escutar HTTP & 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)
3
luofei614

Eu uso a solução proposta pelo Basarat, mas também preciso substituir a porta porque eu costumava ter duas portas diferentes para os protocolos HTTP e HTTPS.

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

Eu prefiro também usar não porta padrão para iniciar o nodejs sem privilégios de root. Eu gosto de 8080 e 8443 porque eu venho 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 digitar 80 e 443 tráfego 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
3
Lorenzo Eccher

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

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

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

Aqui está o jeito que eu fiz: (usando o restify.js, mas deveria funcionar para o express.js, ou o nó em si também)

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

1
awaage