使用 grunt-connect-proxy 把 /api 转发到后端并附带 JWT

解读

国内前端项目普遍采用“本地 dev-server + 后端联调”模式,grunt-connect-proxy 是 Grunt 生态里最常用的代理插件,用来把本地开发服务器(grunt-contrib-connect)的特定路径转发到真正的后端接口。
面试官问“附带 JWT”,核心想考察两点:

  1. 能否正确配置代理规则,把 /api 开头的请求无缝转发到后端域名;
  2. 能否在转发时动态注入 Authorization 头,而不是把 token 写死在配置文件里,避免泄漏。
    现场如果只说“装插件、配 target”只能拿 60 分,必须给出可灰度、可切换、可安全注入的完整方案,才能体现资深工程经验。

知识点

  1. grunt-connect-proxy 的 context、host、port、changeOrigin、headers、rewrite 等关键字段含义;
  2. Gruntfile.js 里通过 template 语法读取环境变量或外部文件,实现 token 与代码分离;
  3. 利用 httpHeaders 选项或 middleware 阶段拦截,在每次请求前动态插入 Authorization;
  4. 与 grunt-contrib-connect 的 livereload 共存时,代理规则顺序必须放在 livereload 之前,否则会被静态中间件短路;
  5. 国内常见后端网关要求 X-Forwarded-ForX-Real-IP 透传,需在 headers 里补全;
  6. 安全规范:JWT 不得提交到代码仓库,必须通过 .env.local 或 CI 变量注入,并在 .gitignore 中排除。

答案

  1. 安装依赖
npm i -D grunt-contrib-connect grunt-connect-proxy dotenv
  1. 在项目根目录建 .env.local(已加入 .gitignore)
VUE_APP_API_HOST=https://gateway.xxx.com
VUE_APP_API_JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  1. Gruntfile.js 关键片段
require('dotenv').config({ path: '.env.local' });

module.exports = function(grunt) {
  grunt.initConfig({
    connect: {
      options: {
        port: 9000,
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function(connect, options, middlewares) {
            // 1. 把代理中间件插到最前面
            middlewares.unshift(
              require('grunt-connect-proxy/lib/utils').proxyRequest
            );
            // 2. 继续挂载静态文件与 livereload
            middlewares.push(connect.static('.tmp'));
            middlewares.push(connect().use(
              '/bower_components',
              connect.static('./bower_components')
            ));
            middlewares.push(connect.static('app'));
            return middlewares;
          }
        }
      },
      proxies: [{
        context: '/api',
        host: process.env.VUE_APP_API_HOST.replace(/^https?:\/\//, ''),
        port: 443,
        https: true,
        changeOrigin: true,
        // 3. 动态注入 JWT
        headers: {
          'Authorization': 'Bearer ' + process.env.VUE_APP_API_JWT,
          'X-Real-IP': '127.0.0.1'
        },
        rewrite: {
          '^/api': '/api/v1'   // 如果后端统一带版本号
        }
      }]
    },

    watch: {
      livereload: {
        options: { livereload: '<%= connect.options.livereload %>' },
        files: ['app/**/*']
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-connect-proxy');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('serve', function(target) {
    grunt.task.run([
      'configureProxies:server',
      'connect:livereload',
      'watch'
    ]);
  });
};
  1. 启动
npx grunt serve

此时访问 http://localhost:9000/api/user/info 会被代理到 https://gateway.xxx.com/api/v1/user/info,并自动携带 Authorization: Bearer <JWT>

拓展思考

  1. 多环境切换:把 .env.local 拆成 .env.dev.env.sit.env.uat,在 CI 里通过 grunt serve --env=sit 读取对应文件,实现一键切换后端与 token
  2. token 续期:开发阶段 JWT 有效期短,可写一个 grunt-task-jwt-refresh,监听 401 响应,自动调用登录接口刷新 token 并回写 .env.local,避免手动复制。
  3. 微服务场景:如果 /api/order/api/pay 指向不同域名,可在 proxies 数组里配多条规则,利用 context 优先级保证匹配顺序;或者引入 http-proxy-middleware 做更细粒度的路径重写。
  4. 安全加固:生产构建时通过 grunt-replaceprocess.env.* 占位符替换成 __ENV__ 标记,由运维在容器启动时利用 sed 注入真实值,实现配置与制品分离,满足国内金融级交付审计要求。