Skip to main content

修改反向代理Header解决源服务器CORS问题

遇上服务器带 CORS 运行,同时前端在 dev(localhost)的时候,访问线上的服务器会出现CORS问题。

以下服务器都为测试服务器,并非实际服务器域名

场景 CORS
线上服务器 production.example.net
本地前端Origin localhost:3000

上表格显示服务器验证的CORS跟本地前端的Origin不一样,所以前端向服务器发送 XHR 请求时会被CORS阻拦。

反向代理的工作

1. 前端发送请求

前端Origin 反代 线上服务器
localhost:3000 ❌ 不匹配,响应空的CORS
localhost:3000 Origin 改为 production.example.net ✅ 匹配,返回 production.example.net

根据上表,当发起请求的时候,反代的工作是在代理的时候,将Origin写成线上服务器匹配的域。

2. 前端接收响应

线上服务器 反代 前端
返回正常数据 ❌ 因为CORS为空,浏览器阻止请求
返回正常数据 CORS改为* ✅ CORS匹配本地域,请求有效

根据上表,当收到响应的时候,反代的工作是将响应头的CORS改为前端匹配的域。

Nginx Conf

# Nginx 运行在前端所在的机器,不加密的HTTP协议
server {
  # 监听8000端口
  listen *:8000;
  # default_server表示无论以IP地址还是域名访问nginx,都处理这个请求
  server_name default_server;

  location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    
    # 改写收到的请求,让线上服务器识别
    proxy_set_header Origin https://production.example.net;

    # 先隐藏线上服务器发来的CORS
    proxy_hide_header 'access-control-allow-origin';
    # 再添加自定义的CORS响应头
    # always 表示不管线上服务器是否返回200都加上这个头
    add_header 'access-control-allow-origin' '*' always;

    # 代理线上服务器的流量
    proxy_pass https://production.example.net;
  }

}

Node 版

Nginx 简单配之后可以在 docker 里面运行。这里的 NodeJS 适用没有 docker 的开发环境。

var http = require('http'), https = require('https'), uri = require('url')

// 目标
var TARGET = 'https://example.com'
// 反代端口
var SERVER_PORT = 8000

var server = http.createServer()

server.on('request', function(req, rsp) {
  // 移除带有前端Origin的Header
  delete req.headers['host']
  delete req.headers['referer']
  delete req.headers['origin']
  if (req.method === 'OPTIONS') return rsp.writeHead(200,{
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': '*',
    'Access-Control-Allow-Methods': '*',
  }).end()

  var url = uri.parse(TARGET + req.url)
  var proxy = https.request({
    ...url,
    method: req.method,
    headers: req.headers,
  }, function(proxyRsp) {
    rsp.setHeader('Access-Control-Allow-Origin', '*')
    rsp.setHeader('Access-Control-Allow-Headers', '*')
    rsp.setHeader('Access-Control-Allow-Methods', '*')
    for (var key in proxyRsp.headers) {
      rsp.setHeader(key, proxyRsp.headers[key])  
    }
    rsp.writeHead(proxyRsp.statusCode)

    proxyRsp
      .on('data', function (chunk) { rsp.write(chunk) })
      .on('end', function () { rsp.end() })
  })
  // 反代的请求出错 = 500
  proxy.once('error', function (e) {
    console.error('Proxy error:', e.toString())
    rsp.writeHead(500).end()
  })
  // 前端关掉了请求,则反代的请求也关掉
  rsp.on('close', function () { proxy.destroy()})
  // 把前端的数据转发到反代
  req
    .on('data', chunk => proxy.write(chunk))
    .on('end', () => proxy.end())
})

server.listen(SERVER_PORT)

参考

How to add a response header on nginx when using proxy_pass?