如何启用 HTTPS 自签名证书并忽略浏览器警告

解读

面试官抛出此题,表面问“HTTPS 自签”,实则考察三点:

  1. 是否理解 Grunt 生态里本地开发服务器插件(grunt-contrib-connect、grunt-browser-sync、grunt-webpack-dev-server 等)的 https 选项设计
  2. 能否在 中国国内常见企业内网环境(无外网、域名严格备案、Chrome/Edge 国行版安全策略更严) 下,给出合规、可脚本化的自签方案;
  3. 是否具备 “让团队零配置一键开发” 的工程化思维,而不是手动点掉浏览器警告这种“临时方案”。
    回答时务必把“生成证书→注入 Grunt→消除警告→固化成任务”四步讲透,并强调 “只在 localhost/127.0.0.1 使用,绝不对外” 的底线,避免合规风险。

知识点

  1. grunt-contrib-connectprotocol: 'https'key/cert 字段
  2. Node 内置 crypto 模块node-forge 在 Gruntfile 里动态生成自签证书
  3. Chrome 国行版 127 起强制要求 SAN(SubjectAltName),否则就算点“高级”也无法继续
  4. mkcert 国内镜像源(淘宝 npm 镜像托管)与 CAROOT 环境变量 自动信任机制
  5. Grunt 任务钩子grunt.event.on('connect.listening', …) 里自动把根证书写入系统存储(Win: certutil,macOS: security,Linux: trust)
  6. 浏览器忽略警告的三种正规手段
    • 把根证书导入“受信任的根证书颁发机构”
    • 使用 --ignore-certificate-errors 仅在内网 CI 容器里运行 headless 测试
    • 通过 chrome://flags/#allow-insecure-localhost 仅限 localhost 豁免(需用户手动开一次)
  7. 团队协作:把 certs/ 目录写进 .gitignore,但提供 grunt cert 一次性生成脚本,保证新人 npm install && grunt cert && grunt serve 即可

答案

  1. 安装依赖

    npm i -D grunt-contrib-connect mkcert@latest
    

    国内网络加 --registry=https://registry.npmmirror.com

  2. 在 Gruntfile 里增加 cert 任务,动态生成含 SAN 的证书

    const mkcert = require('mkcert');
    grunt.registerTask('cert', '生成自签根证书与站点证书', async function() {
      const done = this.async();
      try {
        // 创建本地 CA
        const ca = await mkcert.createCA({
          organization: 'FrontEnd-Team-Local',
          countryCode: 'CN',
          state: 'Beijing',
          locality: 'Beijing',
          validity: 365 * 3
        });
        // 创建叶证书,SAN 必须包含 localhost 与本地 IP
        const cert = await mkcert.createCert({
          ca: { key: ca.key, cert: ca.cert },
          domains: ['127.0.0.1', 'localhost', '*.local.dev'],
          validity: 365
        });
        grunt.file.write('certs/ca.crt', ca.cert);
        grunt.file.write('certs/server.key', cert.key);
        grunt.file.write('certs/server.crt', cert.cert);
        // 自动信任(仅开发机)
        if (process.platform === 'win32') {
          require('child_process').execSync(`certutil -addstore -f "ROOT" "${__dirname}/certs/ca.crt"`);
        } else if (process.platform === 'darwin') {
          require('child_process').execSync(`sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${__dirname}/certs/ca.crt"`);
        }
        grunt.log.ok('证书已生成并信任,下次打开浏览器不再提示警告');
        done();
      } catch (e) {
        grunt.fail.fatal(e);
      }
    });
    
  3. 配置 grunt-contrib-connect 使用刚才的证书

    grunt.initConfig({
      connect: {
        https: {
          options: {
            protocol: 'https',
            port: 8443,
            hostname: '127.0.0.1',
            key: grunt.file.read('certs/server.key'),
            cert: grunt.file.read('certs/server.crt'),
            open: true,
            middleware: function(connect, options, middlewares) {
              // 可继续 push 其他中间件
              return middlewares;
            }
          }
        }
      }
    });
    
  4. 组合任务

    grunt.registerTask('serve', ['cert', 'connect:https:keepalive']);
    
  5. 运行

    grunt serve
    

    浏览器首次打开 https://127.0.0.1:8443无红色警告,地址栏直接显示小锁。

拓展思考

  1. 多子项目复用:把 cert 任务抽成独立 grunt-plugin 并发布到公司私有 npm,统一 CA,避免每人一套根证书。
  2. CI 场景:Docker 容器里无桌面环境,可挂载宿主机已信任的 ca.crt,通过 NODE_EXTRA_CA_CERTS 让 Node 进程也认可,保证 grunt-contrib-connect 的 https 与接口代理同时可信
  3. 合规红线:自签证书仅限 127.0.0.1/localhost.local.dev 这类内部解析域名,切勿把私钥带到公网服务器,否则会被浏览器永久吊销信任。
  4. 未来演进:团队若申请到 .test.local 内网专用域名,可改用 ACME 协议 + 内网 CA(cfssl、step-ca) 实现短周期自动轮换,Grunt 侧只需监听证书更新事件热重启 connect,实现“零信任”内网开发链路