HTTPS In Development: A Practical Guide

2025-05-25

HTTPS 开发:实用指南

根据 Firefox Telemetry 的数据,76% 的网页都使用 HTTPS 加载,而且这个数字还在增长。

软件工程师迟早要处理 HTTPS,而且越早越好。继续阅读,了解为什么以及如何在开发环境中使用 HTTPS 服务 JavaScript 应用程序。

根据 Firefox Telemetry 的数据,HTTPS 采用情况

为什么在开发环境中使用 HTTPS?

首先,你到底应不应该在生产环境中使用 HTTPS 服务网站?除非你真的知道自己在做什么,否则默认答案是肯定的。HTTPS 可以在很多层面提升你的网站:安全性、性能、SEO 等等。

如何设置 HTTPS 通常在首次发布时就已解决,但这也带来了许多其他问题。流量应该从头到尾加密吗?还是加密到反向代理就足够了?证书应该如何生成?证书应该存储在哪里?HSTS又该如何呢?

开发团队应该能够尽早回答这些问题。如果做不到这一点,最终可能会像 Stack Overflow 一样浪费大量时间

此外,将开发环境尽可能靠近生产环境可以降低错误进入生产环境的风险,并减少调试这些错误的时间。这对于端到端测试也是如此。

此外,还有一些功能仅在通过 HTTPS 提供服务的页面上有效,例如Service Workers

但是 HTTPS 很慢!很多人认为加密很复杂,而且从某种程度上来说,为了高效,速度必然很慢。但有了现代硬件和协议,这种想法已经不再成立了

如何为开发环境生成有效证书?

对于生产系统,获取TLS证书很容易:从Let's Encrypt生成一个或从付费提供商处购买一个。

对于开发环境来说,这似乎比较棘手,但其实并不难。

Mkcert:无需思考的 CLI

Filippo Valsorda最近发布了mkcert一个简单的命令行工具,用于生成本地信任的开发证书。你只需运行一行命令即可:

mkcert -install
mkcert example.com
Enter fullscreen mode Exit fullscreen mode

完全支持的证书将在您运行命令的地方可用,即./example.com-key.pem

使用 OpenSSL 手动安装

mkcert应该可以满足您的所有需求,除非您必须与同事共享相同的证书,或者通过本地环境以外的其他系统共享。在这种情况下,您可以使用 生成自己的证书openssl

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt
Enter fullscreen mode Exit fullscreen mode

证书 ( server.crt) 及其密钥 ( server.key) 将是有效的,但属于自签名证书。任何证书颁发机构 ( )都无法识别此证书。但是,所有浏览器都会要求知名的证书颁发机构验证证书,以便接受加密连接。对于自签名证书,它们无法验证,因此会显示一条令人厌烦的警告:

自签名证书错误

您可以接受这种不便,并在每次出现警告时手动忽略它。但这非常繁琐,并且可能会阻止 CI 环境中的端到端测试。更好的解决方案是创建您自己的本地证书颁发机构,将此自定义颁发机构添加到您的浏览器中,并从中生成证书。

这就是mkcert你在幕后所做的事情,但如果你想自己做,我写了一个可能对你有帮助的要点:Kmaschta/205a67e42421e779edd3530a0efe5945

来自反向代理或第三方应用程序的 HTTPS

通常,最终用户不会直接访问应用服务器。相反,用户请求由负载均衡器或反向代理处理,这些代理会在后端之间分配请求、存储缓存、防止不必要的请求等等。这些代理通常还会充当解密请求和加密响应的角色。

在开发环境中,我们也可以使用反向代理!

通过 Traefik 和 Docker Compose 进行加密

Traefik是一款反向代理,它为开发者带来了诸多优势。例如,它配置简单,并带有图形用户界面 (GUI)。此外,Docker Hub 上也提供了官方 Docker 镜像。

因此,让我们在docker-compose.yml仅提供静态文件的假设应用程序中使用它:

version: '3.4'

services:
    reverse-proxy:
        image: traefik # The official Traefik docker image
        command: --docker --api # Enables the web UI and tells Traefik to listen to docker
        ports:
            - '3000:443'  # Proxy entrypoint
            - '8000:8080' # Dashboard
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
            - ./certs/server.crt:/sslcerts/server.crt
            - ./certs/server.key:/sslcerts/server.key
            - ./traefik.toml:/traefik.toml # Traefik configuration file (see below)
        labels:
            - 'traefik.enable=false'
        depends_on:
            - static-files
    static-files:
        image: halverneus/static-file-server
        volumes:
            - ./static:/web
        labels:
            - 'traefik.enable=true'
            - 'traefik.frontend.rule=Host:localhost'
            - 'traefik.port=8080'
            - 'traefik.protocol=http'
        ports:
            - 8080:8080
Enter fullscreen mode Exit fullscreen mode

在此示例中,我们的静态文件服务器监听端口 8080,并通过 HTTP 提供文件服务。此配置指示 Traefik 处理 HTTPS 请求,https://localhost并为每个请求设置代理http://localhost:8080,以便提供静态文件服务。

我们还必须添加一个traefik.toml来配置 Traefik 入口点:

debug = false

logLevel = "ERROR"
defaultEntryPoints = ["https","http"]

[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
  [entryPoints.https.tls]
      [[entryPoints.https.tls.certificates]]
      certFile = "/sslcerts/server.crt"
      keyFile = "/sslcerts/server.key"

Enter fullscreen mode Exit fullscreen mode

这里我们有两个入口点:httphttps,分别监听 80 和 443 端口。第一个重定向到 HTTPS,第二个配置为使用指定的 TLS 证书加密请求。

Traefik 仪表板

通过 Nginx 从 Docker Compose 进行加密

显然,我们可以使用流行的 Nginx 反向代理来实现同样的功能。由于 Nginx 本身也可以直接提供静态文件服务,因此设置起来更简单。同样,第一步是docker-compose.yml

version: '3'

services:
    web:
        image: nginx:alpine
        volumes:
            - ./static:/var/www
            - ./default.conf:/etc/nginx/conf.d/default.conf
            - ../../certs/server.crt:/etc/nginx/conf.d/server.crt
            - ../../certs/server.key:/etc/nginx/conf.d/server.key
        ports:
            - "3000:443"
Enter fullscreen mode Exit fullscreen mode

nginx 配置如下default.conf

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;

    server_name ~.;

    ssl_certificate /etc/nginx/conf.d/server.crt;
    ssl_certificate_key /etc/nginx/conf.d/server.key;

    location / {
        root /var/www;
    }

    ## If the static server was another docker service,
    ## It is possible to forward requests to its port:
    # location / {
    #     proxy_set_header Host $host;
    #     proxy_set_header X-Real-IP $remote_addr;
    #     proxy_pass http://web:3000/;
    # }
}
Enter fullscreen mode Exit fullscreen mode

直接从应用程序提供 HTTPS 服务

有时安全需求会要求端到端加密,或者在开发环境中使用反向代理似乎有些过度。大多数情况下,您可以在日常开发环境中直接使用 HTTPS 服务。

让我们以一个常见的堆栈为例:一个使用express的 REST API 的 React 应用程序。

使用 Create React App 或 Webpack Dev Server

你的普通 React 应用是由 引导的create-react-app。这个很棒的工具自带了很多内置功能,并且可以开箱即用地处理 HTTPS。为此,你只需HTTPS=true在启动应用时指定一个环境变量:

HTTPS=true npm run start
# or
HTTPS=true yarn start
Enter fullscreen mode Exit fullscreen mode

此命令将使用自动生成的证书https://localhost:3000(而非证书本身)为您的应用提供服务http://localhost:3000。但由于证书本身是自签名证书,开发者体验不佳。

如果您想使用自己的 HTTPS 证书(由您的浏览器信任的机构签名),create-react-app则不允许您在没有弹出应用程序的情况下对其进行配置(npm run eject)。

编辑: dev.to 的读者Zwerge 找到了一个巧妙的解决方法,可以动态替换默认的 HTTPS 证书:

  "scripts": {
    "prestart": "(cat ../../certs/server.crt ../../certs/server.key > ./node_modules/webpack-dev-server/ssl/server.pem) || :",
    "start": "react-scripts start",
  },
Enter fullscreen mode Exit fullscreen mode

幸运的是,如果你确实弹出了 CRA,或者你的项目与 webpack 捆绑在一起,那么使用 HTTPS 服务就和使用 HTTPSwebpack-dev-server一样简单!你可以在 Webpack 配置中使用两行代码来配置自定义 HTTPS 证书:create-react-app

const fs = require('fs');
const path = require('path');

module.exports = {
    mode: 'production',
    // ...
    devServer: {
        https: {
            key: fs.readFileSync(path.resolve(__dirname, '../../certs/server.key')),
            cert: fs.readFileSync(path.resolve(__dirname, '../../certs/server.crt')),
        },
        port: 3000,
    },
};
Enter fullscreen mode Exit fullscreen mode

下次运行时webpack-dev-server,它将处理对的 HTTPS 请求https://localhost:3000

示例应用程序 - 静态站点

使用 Express 和 SPDY 加密 HTTP/2

现在我们已经有了通过 HTTPS 提供服务的应用程序前端部分,我们必须对后端执行同样的操作。

为此,我们使用expressspdy。难怪这两个库的名字都与速度有关,因为它们的设置速度很快!

const fs = require('fs');
const path = require('path');
const express = require('express');
const spdy = require('spdy');

const CERTS_ROOT = '../../certs/';

const app = express();

app.use(express.static('static'));

const config = {
    cert: fs.readFileSync(path.resolve(CERTS_ROOT, 'server.crt')),
    key: fs.readFileSync(path.resolve(CERTS_ROOT, 'server.key')),
};

spdy.createServer(config, app).listen(3000, (err) => {
    if (err) {
        console.error('An error occured', error);
        return;
    }

    console.log('Server listening on https://localhost:3000.')
});
Enter fullscreen mode Exit fullscreen mode

HTTP/2 并非提供 HTTPS 服务的必要条件,可以使用 HTTP 开头的协议来提供加密内容。不过,在提供 HTTPS 服务的同时,我们可以升级 HTTP 协议。如果您想了解更多关于 HTTP/2 的优势,可以阅读这篇快速常见问题解答

结论

现代工具能够构建对最终用户更安全、更快速且易于引导的应用程序。我希望我能够说服您从项目伊始就使用这些库和技术,因为它们的安装成本仍然很低。

本文中使用的所有示例都收集在以下仓库中:marmelab/https-on-dev。欢迎大家自由尝试,并添加您自己的 HTTPS 开发经验!

文章来源:https://dev.to/kmaschta/https-in-development-a-practical-guide-175m
PREV
领域驱动设计
NEXT
我每天使用的 VS Code 扩展