Heroku NodeJS强制将HTTP重定向为HTTPS的SSL

111

我在Heroku上使用Node.js中的Express.js搭建了一个应用,并启用了https。我该如何识别协议并强制重定向到https?

我的应用只是一个简单的http服务器,它还没有意识到Heroku正在发送https请求:

// Heroku provides the port they want you on in this environment variable (hint: it's not 80)
app.listen(process.env.PORT || 3000);

8
Heroku的支持团队回答了我的问题,我没有在这里找到已经发布的答案,所以我想将其公开发布并分享这个知识。他们通过在请求头中添加前缀“x-”来传递有关原始请求的许多信息。以下是我现在正在使用的代码(在我的路由定义顶部):app.get('*',function(req,res,next){ if(req.headers['x-forwarded-proto']!='https') res.redirect('https://mypreferreddomain.com'+req.url) else next() }) - Derek Bredensteiner
1
好的,我明白你是这样检查https并在需要时进行重定向的。但是是否有一种方法可以通过您的域名提供商在DNS级别上进行重定向。因此,在浏览器解析DNS之前,它已经处于https状态。因为使用这种方法,我认为根据我的重定向知识,一旦请求在http上进行,然后再次在https上进行。因此,如果发送了敏感数据,则会首先通过http发送,然后再通过https发送。这有点违背了初衷。请告诉我是否有误。 - Muhammad Umer
@MuhammadUmer,你的推理似乎很有道理,你有没有发现更多的东西? - Karoh
我只是将Cloudflare用作名称服务器,它的作用类似于nginx,并且可以通过单击切换按钮来重定向到SSL版本。此外,您也可以使用以下方法实现:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security此外,通常没有人立即发送数据,他们通常会先着陆在表单上,然后再提交。因此,在服务器端代码、DNS服务器、HTTP头和JavaScript中,您可以检查并重定向到HTTPS。https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections - Muhammad Umer
13个回答

113
截至今天,即2014年10月10日,使用Heroku Cedar stackExpressJS ~3.4.4,以下是一组可工作的代码。

这里要记住的主要事情是我们正在部署到Heroku。 SSL终止发生在负载均衡器处,在加密流量到达您的节点应用程序之前。可以使用req.headers ['x-forwarded-proto'] ==='https'测试是否使用https进行请求。

我们不需要担心在应用程序中有本地SSL证书等问题,如果在其他环境中进行托管,则可能需要。但是,如果使用自己的证书、子域等,则应首先通过Heroku Add-ons应用SSL附加组件。

然后,只需添加以下内容即可将任何非HTTPS重定向到HTTPS。

  1. 确保使用“app.use”(对于所有操作,而不仅仅是获取)
  2. 明确将forceSsl逻辑外部化为已声明的函数
  3. 不要在“app.use”与“*”一起使用-当我测试时,这实际上失败了。
  4. 这里,我只想在生产中使用SSL。(根据需要更改)

代码:

 var express = require('express'),
   env = process.env.NODE_ENV || 'development';

 var forceSsl = function (req, res, next) {
    if (req.headers['x-forwarded-proto'] !== 'https') {
        return res.redirect(['https://', req.get('Host'), req.url].join(''));
    }
    return next();
 };

 app.configure(function () {      
    if (env === 'production') {
        app.use(forceSsl);
    }

    // other configurations etc for express go here...
 });

注意针对SailsJS (0.10.x)用户的说明。您可以在api/policies文件夹中创建一个策略文件(enforceSsl.js):


module.exports = function (req, res, next) {
  'use strict';
  if ((req.headers['x-forwarded-proto'] !== 'https') && (process.env.NODE_ENV === 'production')) {
    return res.redirect([
      'https://',
      req.get('Host'),
      req.url
    ].join(''));
  } else {
    next();
  }
};

然后从config/policies.js中引用以及其他任何策略,例如:

'*': ['authenticated', 'enforceSsl']


1
关于使用 sails 策略的说明:如 http://sailsjs.org/#/documentation/concepts/Policies 所述:“默认策略映射不会‘级联’或‘向下渗透’。控制器操作的指定映射将覆盖默认映射。”这意味着,一旦您有其他特定控制器/操作的策略,您必须确保在这些控制器/操作上添加 'enforceSsl'。 - Manuel Darveau
2
以下表格列出了Express 4中的其他小但重要的更改:... app.configure()函数已被删除。使用process.env.NODE_ENV或app.get('env')函数检测环境并相应地配置应用程序。 - Kevin Wheeler
10
另外,请注意res.redirect在Express 4.x中默认为302重定向。出于SEO和缓存的原因,您可能需要使用301重定向。请将相应的行替换为return res.redirect(301, ['https://', req.get('Host'), req.url].join('')); - Kevin Wheeler
8
注意:在 Express 4.x 中,删除 app.configure 行并只使用内部代码块。app.configure 是过时的代码,在 Express 中不再包含。 - Augie Gardner

102
答案是使用Heroku传递的'x-forwarded-proto'标头,因为它会执行代理。 (附注:他们还传递了几个其他的x-变量,可能会很方便,请查看)。
我的代码:
/* At the top, with other redirect methods before other routes */
app.get('*',function(req,res,next){
  if(req.headers['x-forwarded-proto']!='https')
    res.redirect('https://mypreferreddomain.com'+req.url)
  else
    next() /* Continue to other routes if we're not redirecting */
})

感谢Brandon,我一直在等那个6小时的延迟结束,这样我就可以回答自己的问题了。


4
这样做会不会允许除了“GET”方法之外的其他方法通过? - Jed Schmidt
1
@Aaron:如果您透明地重定向POST请求,可能会丢失信息。我认为对于HTTP的其他请求,您应该返回400。 - theodorton
4
如果你只想在生产环境中运行,可以在条件语句中加入 && process.env.NODE_ENV === "production" - keepitreal
307(重定向并保持请求方法)可能比400错误更好。 - Beni Cherniavsky-Paskin
这个答案存在多个问题,请查看下一个答案(https://dev59.com/EWw05IYBdhLWcg3whCRn#23894573),并将此答案评为负面。 - Neil
这适用于服务器,但不适用于客户端。我们如何在客户端上重定向http到https,例如Next.js React应用程序? - 5tormTrooper

23

被接受的答案中使用了硬编码域名,如果你在多个域名下使用相同的代码(例如:dev-yourapp.com、test-yourapp.com、yourapp.com),这并不太好。

改用以下代码:

/* Redirect http to https */
app.get("*", function (req, res, next) {

    if ("https" !== req.headers["x-forwarded-proto"] && "production" === process.env.NODE_ENV) {
        res.redirect("https://" + req.hostname + req.url);
    } else {
        // Continue to other routes if we're not redirecting
        next();
    }

});

https://blog.mako.ai/2016/03/30/redirect-http-to-https-on-heroku-and-node-generally/


运行正常。我不知道为什么我只需要将 req.hostname 替换为 req.headers.host,也许是因为我使用的是Express版本4.2。 - Jeremy Piednoel

17

6

如果您想在本地测试x-forwarded-proto头,请使用Nginx设置vhost文件,将所有请求代理到您的节点应用程序。您的Nginx vhost配置文件可能如下所示:

NginX

server {
  listen 80;
  listen 443;

  server_name dummy.com;

  ssl on;
  ssl_certificate     /absolute/path/to/public.pem;
  ssl_certificate_key /absolute/path/to/private.pem;

  access_log /var/log/nginx/dummy-access.log;
  error_log /var/log/nginx/dummy-error.log debug;

  # node
  location / {
    proxy_pass http://127.0.0.1:3000/;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

重要的部分是将所有请求代理到本地主机端口3000(这是您的Node应用程序运行的位置),并设置一堆标头,包括X-Forwarded-Proto
然后在您的应用程序中像往常一样检测该标头。

Express

var app = express()
  .use(function (req, res, next) {
    if (req.header('x-forwarded-proto') == 'http') {
      res.redirect(301, 'https://' + 'dummy.com' + req.url)
      return
    }
    next()
  })

Koa

var app = koa()
app.use(function* (next) {
  if (this.request.headers['x-forwarded-proto'] == 'http') {
    this.response.redirect('https://' + 'dummy.com' + this.request.url)
    return
  }
  yield next
})

主机

最后,您需要将此行添加到您的hosts文件中。

127.0.0.1 dummy.com

6
你应该看一下 heroku-ssl-redirect,它能够完美地解决问题!
var sslRedirect = require('heroku-ssl-redirect');
var express = require('express');
var app = express();

// enable ssl redirect
app.use(sslRedirect());

app.get('/', function(req, res){
  res.send('hello world');
});

app.listen(3000);

4
如果您正在使用cloudflare.com作为CDN,并与heroku搭配使用,您可以轻松地在cloudflare中启用自动SSL重定向,步骤如下:
  1. 登录并进入您的仪表板
  2. 选择页面规则

    选择页面规则

  3. 添加您的域名,例如www.example.com,并将始终使用https切换到“开” 将始终使用https切换到“开”

3

Loopback用户可以使用略微改进的arcseldon答案作为中间件:

server/middleware/forcessl.js

module.exports = function() {  
  return function forceSSL(req, res, next) {
    var FORCE_HTTPS = process.env.FORCE_HTTPS || false;
      if (req.headers['x-forwarded-proto'] !== 'https' && FORCE_HTTPS) {
        return res.redirect(['https://', req.get('Host'), req.url].join(''));
      }
      next();
    };
 };

server/server.js

var forceSSL = require('./middleware/forcessl.js');
app.use(forceSSL());

2

我正在使用Vue、Heroku,与您遇到了相同的问题:

我已按照以下方式更新了我的server.js文件,并且不再修改它,因为它现在可以正常工作:):

const serveStatic = require('serve-static')
const sts = require('strict-transport-security');
const path = require('path')

var express = require("express");

require("dotenv").config();
var history = require("connect-history-api-fallback");

const app = express()
const globalSTS = sts.getSTS({'max-age':{'days': 365}});
app.use(globalSTS);

app.use(
  history({
    verbose: true
  })
);

app.use((req, res, next) => {
  if (req.header('x-forwarded-proto') !== 'https') {
    res.redirect(`https://${req.header('host')}${req.url}`)
  } else {
    next();
  }
});

app.use('/', serveStatic(path.join(__dirname, '/dist')));
app.get(/.*/, function (req, res) {
res.sendFile(path.join(__dirname, '/dist/index.html'))
})

const port = process.env.PORT || 8080
app.listen(port)
console.log(`app is listening on port: ${port}`)

2
这是一种更专门针对 Express 的方法来实现这个功能。
app.enable('trust proxy');
app.use('*', (req, res, next) => {
  if (req.secure) {
    return next();
  }
  res.redirect(`https://${req.hostname}${req.url}`);
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接