使用node.js/express自动进行HTTPS连接/重定向



我一直在尝试用我正在处理的node.js项目设置HTTPS

// 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 worldn");
}).listen(8000);

现在,当我做时

curl -k https://localhost:8000/

我得到

hello world

正如预期的那样。但如果我做

curl -k http://localhost:8000/

我得到

curl: (52) Empty reply from server

回想起来,很明显,它会以这种方式工作,但与此同时,最终访问我的项目的人不会键入https://yadayada,我希望从他们访问网站的那一刻起,所有流量都是https。

我如何让node(和Express,因为这是我正在使用的框架(将所有传入流量切换到https,无论是否指定了它?我找不到任何关于这方面的文件。还是仅仅假设在生产环境中,节点前面有一些东西(例如nginx(来处理这种重定向?

这是我第一次涉足网络开发,所以如果这是显而易见的,请原谅我的无知。

Ryan,感谢您为我指明了正确的方向。我用一些代码稍微充实了一下你的答案(第2段(,它很有效。在这个场景中,这些代码片段被放在我的快递应用程序中:

// set up plain http server
var http = express();
// 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 express服务器监听3000上的ATM。我设置了这些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

总之,这正是我想要的。

为了防止通过HTTP窃取cookie,请参阅此答案(来自评论(或使用此代码:

const session = require('cookie-session');
app.use(
  session({
    secret: "some secret",
    httpOnly: true,  // Don't let browser javascript access cookies.
    secure: true, // Only use cookies over https.
  })
);

感谢这个家伙:https://www.tonyerwin.com/2014/09/redirecting-http-to-https-with-nodejs.html

如果安全,则通过https请求,否则重定向到https

app.enable('trust proxy')
app.use((req, res, next) => {
    req.secure ? next() : res.redirect('https://' + req.headers.host + req.url)
})

如果您使用传统端口,因为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通过同一端口

使用Nginx,您可以利用"x-forwardedproto"标头:

function ensureSec(req, res, next){
    if (req.headers["x-forwarded-proto"] === "https"){
       return next();
    }
    res.redirect("https://" + req.headers.host + req.url);  
}

截至0.4.12,我们没有真正干净的方法来侦听HTTP&使用Node的HTTP/HTTPS服务器的同一端口上的HTTPS。

有些人已经解决了这个问题,让Node的HTTPS服务器(这也适用于Express.js(监听443(或其他端口(,还让一个小型http服务器绑定到80,并将用户重定向到安全端口。

如果你必须能够在一个端口上处理这两种协议,那么你需要在该端口上放置nginx、lighttpd、apache或其他一些web服务器,并充当Node的反向代理。

我使用Basarat提出的解决方案,但我也需要覆盖端口,因为我曾经有两个不同的端口用于HTTP和HTTPS协议。

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

我也更喜欢使用非标准端口,以便在没有root权限的情况下启动nodejs。我喜欢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);

然后,我在HTTP和HTTPS端口上使用iptable来处理forwording 80和443流量。

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

您可以使用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);

我发现req.protocol在使用express时有效(没有测试过,但我怀疑它有效(。使用当前节点0.10.22和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();
  } 
});

需要更新此答案才能使用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);

如果您的应用程序位于受信任的代理(例如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("信任代理"(允许express检查X-Forwarded-Proto标头

您可以使用"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 worldn");
}).listen(handle);
http.createServer(function(req,res){
  res.writeHead(200);
  res.end("hello worldn");
}).listen(handle)

这里的大多数答案都建议使用req.headers.host标头。

Host标头是HTTP 1.1所必需的,但它实际上是可选的,因为标头可能不是由HTTP客户端实际发送的,node/express将接受此请求。

您可能会问:哪个HTTP客户端(例如:浏览器(可以发送缺少该标头的请求?HTTP协议非常琐碎。您可以在几行代码中创建一个HTTP请求,而不发送主机头,如果每次收到格式错误的请求时都会抛出异常,根据您处理此类异常的方式,这可能会导致服务器停机。

因此始终验证所有输入。这不是妄想症,我收到的请求在我的服务中缺少主机头。

此外,从不将URL视为字符串。使用节点url模块可以修改字符串的特定部分。将URL视为字符串可以通过多种方式加以利用。不要这样做。

var express = require('express');
var app = express();
app.get('*',function (req, res) {
    res.redirect('https://<domain>' + req.url);
});
app.listen(80);

这就是我们使用的,而且效果很好!

这对我很有效:

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

加载页面时,此脚本会保存URL页面并检查地址是https还是http。如果是http,脚本会自动将您重定向到相同的https页面

(function(){
  var link = window.location.href;
  if(link[4] != "s"){
    var clink = "";
    for (let i = 4; i < link.length; i++) {
      clink += link[i];
    }
    window.location.href = "https" + clink;
  }
})();

您可以实例化两个Node.js服务器,其中一个用于HTTP和HTTPS

您还可以定义两个服务器都将执行的设置函数,这样您就不必编写太多重复的代码。

我是这样做的:(使用restify.js,但应该适用于express.js,或者节点本身(

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

更新了jake答案的代码。将其与https服务器一起运行。

// set up plain http server
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app);
// set up a route to redirect http to https
app.get('*', function(req, res) {
  res.redirect('https://' + req.headers.host + req.url);
})
// have it listen on 80
server.listen(80);

这对我来说适用于express:

app.get("*",(req,res,next) => {
    if (req.headers["x-forwarded-proto"]) {
        res.redirect("https://" + req.headers.host + req.url)
    }
    if (!res.headersSent) {
        next()
    }
})

将其放在所有HTTP处理程序之前。

这对我很有效:

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

其想法是检查传入请求是否使用https发出,如果是,请不要再次将其重定向到https,而是照常继续。否则,如果是http,则使用附加的https重定向。

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

经过多年从http到https的完美重定向研究,我在这里找到了完美的解决方案。

const http = require("http");
const https = require("https");
const { parse } = require("url");
const next = require("next");
const fs = require("fs");
const ports = {
http: 3000,
https: 3001
}
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
const httpsOptions = {
key: fs.readFileSync("resources/certificates/localhost-key.pem"),
cert: fs.readFileSync("resources/certificates/localhost.pem")
};
// Automatic HTTPS connection/redirect with node.js/express
// source: https://stackoverflow.com/questions/7450940/automatic-https- 
connection-redirect-with-node-js-express
app.prepare().then(() => {
// Redirect from http port to https
http.createServer(function (req, res) {
    res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(ports.http, ports.https) + req.url });
    console.log("http request, will go to >> ");
    console.log("https://" + req.headers['host'].replace(ports.http, ports.https) + req.url);
    res.end();
}).listen(ports.http, (err) => {
    if (err) throw err;
    console.log("ready - started server on url: http://localhost:" + ports.http);
});
https.createServer(httpsOptions, (req, res) => {
    const parsedUrl = parse(req.url, true);
    handle(req, res, parsedUrl);
}).listen(ports.https, (err) => {
    if (err) throw err;
    console.log("ready - started server on url: https://localhost:" + ports.https);
});
});

在我的情况下,我必须同时更改端口并侦听两个端口:

appr.get("/", (req, res) => {
  res.redirect('https://' + req.headers['host'].replace(PORT, PORTS) + req.url);
});

如果您的节点应用程序安装在IIS上,您可以在web.config 中这样做

<configuration>
    <system.webServer>
        <!-- indicates that the hello.js file is a node.js application 
    to be handled by the iisnode module -->
        <handlers>
            <add name="iisnode" path="src/index.js" verb="*" modules="iisnode" />
        </handlers>
        <!-- use URL rewriting to redirect the entire branch of the URL namespace
    to hello.js node.js application; for example, the following URLs will 
    all be handled by hello.js:
    
        http://localhost/node/express/myapp/foo
        http://localhost/node/express/myapp/bar
        -->
        <rewrite>
            <rules>
                <rule name="HTTPS force" enabled="true" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions>
                        <add input="{HTTPS}" pattern="^OFF$" />
                    </conditions>
                    <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" />
                </rule>
                <rule name="sendToNode">
                    <match url="/*" />
                    <action type="Rewrite" url="src/index.js" />
                </rule>
            </rules>
        </rewrite>
        <security>
            <requestFiltering>
                <hiddenSegments>
                    <add segment="node_modules" />
                </hiddenSegments>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>

最新更新