node.js / express와의 자동 HTTPS 연결 / 리디렉션


182

작업중 인 node.js 프로젝트로 HTTPS를 설정하려고했습니다. 필자는 이 예제 에서 본질적으로 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가되기를 바랍니다. 사이트.

모든 수신 트래픽을 지정했는지 여부에 관계없이 모든 들어오는 트래픽을 https로 전달하기 위해 노드 (및 사용중인 프레임 워크이므로 Express)를 얻는 방법은 무엇입니까? 이 문제를 해결 한 문서를 찾을 수 없습니다. 또는 프로덕션 환경에서 노드가 이런 종류의 리디렉션을 처리하는 노드 (예 : nginx) 앞에있는 것으로 가정합니까?

이것은 웹 개발에 대한 첫 번째 길이므로, 이것이 분명하다면 내 무지를 용서하십시오.


간결하게 여기에 대답했습니다 : stackoverflow.com/a/23894573/1882064
arcseldon

3
누구 배포하는 Heroku가 들어 도움이되지 않습니다이 질문에 대한 대답은하지만 (당신은 "너무 많은 리디렉션"얻을 것이다) 이 대답 것이다 또 다른 질문에서
리코 칼러

답변:


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에서 ATM을 수신합니다. 노드가 루트로 실행될 필요가 없도록 iptables 규칙을 설정했습니다.

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
정말 중요한 질문 (보안 관련). 리디렉션이 실제로 발생하기 전에 공격자가 쿠키 (세션 ID)를 스니핑하고 훔칠 수 있습니까?
Costa

4
이 결과 증상을 어떻게 해결합니까? Error 310 (net::ERR_TOO_MANY_REDIRECTS): There were too many redirects
bodine

16
실제로, 이것은 더 나은 것 같습니다 ... 그냥 리디렉션을 감싸십시오if(!req.secure){}
bodine

6
- 난 그냥 보안에 대한 코스타의 중요한 질문은 다른 게시물에 주어진 @에 대한 해답을 지적 할 stackoverflow.com/questions/8605720/...
ThisClark

2
if(req.protocol==='http')성명 을 감싸고 싶을 수도 있습니다
Max

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.)301클라이언트에게 동일한 방법을 사용하도록 지시하지 않기 때문에 GET 호출에 대해서만 올바르게 작동합니다 . 사용 된 방법 (및 다른 모든 매개 변수)을 유지하려면을 사용해야 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
이것이 가장 좋은 대답입니다!
Steffan

3
동의, 첫 번째 답변이되어야합니다.
1984

12
다음은이 솔루션이 저에게 도움이 app.enable('trust proxy');
되었음을 보여줍니다

2
위에서 언급 한대로 ^^^^ : 트래픽을 리디렉션하는 프록시, 방화벽 또는로드 밸런서 뒤에있는 경우 '신뢰 프록시'가 필요합니다. 예 : 모든 http 및 https 요청을 동일한 비 루트 포트로 전송하는 AWS로드 밸런싱 (예 : 3000)을 VPC 웹 서버에서
alanwaring

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가 작동했습니다!
Zugwalt

나는 똑같은 것을 발견했다, 이것은 매우 신뢰할 수없는 것처럼 보인다.
dacopenhagen

2
req.secure는 올바른 방법이지만 req.secure는 proto == "https"와 동일하기 때문에 프록시 뒤에 있으면 버그가 있습니다. 그러나 프록시 익스프레스 뒤에는 프로토가 https, http라고 말할 수 있습니다.
dacopenhagen

7
app.enable('trust proxy');"앱이 전면 프록시 뒤에 있고 X-Forwarded- * 헤더를 사용하여 클라이언트의 연결 및 IP 주소를 확인 해야 함을 나타냅니다." expressjs.com/en/4x/api.html#app.set
dskrvk

URL을 문자열로 취급하지 말고 대신 url 모듈을 사용하십시오.
arboreal84

12

0.4.12 현재 Node의 HTTP / HTTPS 서버를 사용하여 동일한 포트에서 HTTP 및 HTTPS를 청취하는 확실한 방법은 없습니다.

일부 사람들은 Node의 HTTPS 서버 (Express.js와 함께 작동)가 443 (또는 다른 포트)을 수신하고 작은 http 서버가 80에 바인딩되고 사용자를 보안 포트로 리디렉션하도록하여이 문제를 해결했습니다.

단일 포트에서 두 프로토콜을 모두 처리 할 수 ​​있어야하는 경우 해당 포트에 nginx, lighttpd, apache 또는 다른 웹 서버를 배치하고 노드의 역방향 프록시로 작동해야합니다.


고마워 라이언. "... 작은 http 서버가 80에 바인드되고 리디렉션됩니다 ..."라는 말은 다른 노드 http 서버 를 의미한다고 가정 합니다. 이것이 어떻게 작동하는지에 대한 아이디어가 있다고 생각하지만 예제 코드를 가리킬 수 있습니까? 또한이 문제에 대한 "깨끗한"솔루션이 노드 로드맵 어디에 있는지 알고 있습니까?
Jake

Node.js 애플리케이션에는 여러 개의 http (s) 서버가있을 수 있습니다. Node.js 문제 ( github.com/joyent/node/issues )와 메일 링리스트 ( groups.google.com/group/nodejs )를 확인했지만보고 된 문제가 나타나지 않았지만 문제에 대한 몇 개의 게시물을 보았습니다 메일 링리스트에. 내가 알 수있는 한 이것은 파이프에 없다. github 에보 고하고 어떤 피드백을 받는지 확인하는 것이 좋습니다.
Ryan Olds

정말 중요한 질문 (보안 관련). 리디렉션이 실제로 발생하기 전에 "공격자"가 쿠키 (세션 ID)를 스니핑하고 훔칠 수 있습니까?
Costa

예, 3xx 상태 코드 및 가능하면 Location 헤더가 에이전트로 다시 전송되며, 이때 에이전트는 Location 헤더에 지정된 URL을 요청합니다.
Ryan Olds

2015 년인데, 여전히 그렇습니까?
TheEnvironmentalist

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에서 문제가 발생했습니다.
Martavis P.

1
자매 패키지 : npmjs.com/package/express-to-https -하지만 / isbetter / 등 작동하는지 난 몰라
케찰코아틀

정적 파일 (단일 페이지 앱 포함)을 제공하기 위해 미들웨어를 사용하는 경우 모든 리디렉션 미들웨어를 app먼저 첨부해야합니다 . ( 이 답변 참조 )
Matthew R.

9

Basarat에서 제안한 솔루션을 사용하지만 HTTP 및 HTTPS 프로토콜에 2 개의 다른 포트가 있었기 때문에 포트를 덮어 써야합니다.

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

루트 권한없이 nodejs를 시작하려면 표준 포트가 아닌 것을 선호합니다. 나는 바람둥이에 대한 다년간의 프로그래밍에서 왔기 때문에 8080과 8443을 좋아합니다.

완성 된 파일이됩니다

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

그런 다음 HTTP 및 HTTPS 포트에서 80 및 443 트래픽을 forwording하기 위해 iptable을 사용합니다.

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, 이것은 나를 위해 일했다. 옵션을 사용하는 대신 letsencrypt를 사용했습니다. var 옵션을 삭제했습니다. letsencrypt 매개 변수 (privateKey, 인증서, ca 및 자격 증명)를 추가하고 옵션을 자격 증명으로 변경했습니다. https.createServer (credentials, app)
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 ( 'trust proxy')은 Express가 X-Forwarded-Proto 헤더를 확인하도록합니다.

6

Express를 사용할 때 req.protocol이 작동한다는 것을 알았습니다 (없이 테스트하지는 않았지만 작동한다고 생각합니다). Express 3.4.3과 함께 현재 노드 0.10.22 사용

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 클라이언트가 헤더를 보내지 않을 수 있으므로 node / express는이 요청을 수락하므로 실제로 선택 사항입니다.

다음과 같은 질문을 할 수 있습니다 : 어떤 HTTP 클라이언트 (예 : 브라우저)가 해당 헤더가없는 요청을 보낼 수 있습니까? HTTP 프로토콜은 매우 사소합니다. 몇 줄의 코드로 HTTP 요청을 작성하여 호스트 헤더를 보내지 않을 수 있으며, 잘못된 요청을받을 때마다 예외가 발생하며 이러한 예외를 처리하는 방법에 따라 서버가 중단 될 수 있습니다.

따라서 항상 모든 입력의 유효성을 검사하십시오 . 이것은 편집증이 아니며 서비스에 호스트 헤더가 부족한 요청을 받았습니다.

또한 URL을 문자열로 취급하지 마십시오 . 노드 URL 모듈을 사용하여 문자열의 특정 부분을 수정하십시오. URL을 문자열로 취급하는 것은 여러 가지 방법으로 악용 될 수 있습니다. 하지마


예를 들었다면 당신의 대답은 완벽 할 것입니다.
SerG

합법적으로 들리지만 일부 참조 / 링크는 훌륭합니다.
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
이것은 무한 루프를 일으키지 않습니까?
Muhammad Umer 2016 년

아니요, 포트 80에서만 수신하고 포트 443으로 전송하기 때문입니다. 무한 루프가 발생하는 유일한 방법은 443의 일부 리스너가 80으로 다시 리디렉션되어 내 코드에없는 경우입니다. 나는 그것이 의미가 있기를 바랍니다. 감사!
Nick Kotenberg

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는 작동하지 않습니다. :(
Joe

1
설명 된대로 작동하지 않습니다. Joe가 본 문제를 확인할 수 있습니다.
스테판 Mazurov

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

이것은 옳을 지 모르지만 이것이 질문자의 질문에 어떻게 대답하는지 자세히 설명해야합니다.
Joe C

1

2 개의 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/

Express 4.17.1을 사용합니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.