https://juejin.cn/post/6995374680114741279

  

编写代码两分钟,解决跨域两小时,我吐了。

如果对跨域还不了解的朋友,可以看这篇:【基础】HTTP、TCP/IP 协议的原理及应用

最近一段时间,在搞一个 SDK 的项目,使用的 TS + rolluprollup 相比于 webpack 来说,更利于编写底层库,但是劣势也比较明显,其中之一就是它没有 webpack-dev-server 那样强大的插件来支持本地开发时的服务代理。随之而来的就是一个非常棘手的问题,跨域要怎么解决?

解决跨域的难点

  1. rollup 并没有非常好用的开发时代理插件,能有效解决大部分跨域问题。
  2. 在这个 SDK 项目中,涉及到与多个不同 server 的交互,每个 server 对于跨域的支持各不相同(大部分是根本不支持跨域);对于 request method 的支持也各不相同(有的甚至不支持 OPTIONS 请求)
  3. 不同的 server 不同的接口,对于 cookie 的要求也不相同,有些接口涉及到 cookie 的跨域传输。
  4. 不同的 server 在不同的部门,沟通成本巨大,而且让每个 team 去修改代码,开放跨域协助我本地调试也不现实。

综合上面几点考虑,插件是不能用了,又不想用 node 搭建代理服务。没办法,只好搞一波 Nginx 。

Nginx 反向代理

我对 nginx 研究不深,目的是只要能帮我解决问题即可,所以就写下我用到的配置,避免下次再遇到同样的问题还要看文档 google。

Nginx 配置文件

在 Nginx 的安装目录下,有个 conf 文件夹,里面有个 nginx.conf,就是这个文件。

Nginx 常用命令

到 Nginx 安装目录下打开 bash

  1. ./nginx.exe :用于运行 Nginx ,如果没有改动过配置文件的端口号什么的话,默认代理到 http://localhost:80,浏览器打开输入 http://localhost 能看到 Nginx 的启动页即为成功。
  2. ./nginx.exe -s quit:用于退出 Nginx
  3. ./nginx.exe -s reload:用于重启 Nginx,这个一般用于更改 Nginx 的配置项后,需要重启。
  4. ./nginx.exe -t 用于检查 Nginx 的配置有没有问题,一般在改过 Nginx 配置后,可以调用检查一遍。

如果已经把 Nginx 配置为全局变量,则不需要在 Nginx 的安装目录下开启 bash ,随便哪开都可以访问到,并且直接输入 nginx 即可代表 ./nginx.exe

Nginx 日志

会看 Nginx 日志还是挺重要的,很多问题都可以从日志里排查出来。Nginx 有两种日志:logs/error.log 和 logs/access.log,分别记录了 Nginx 本身的错误日志,及接口请求的日志。

一般对开发有用的是 access.log ,一条日志结构如下:

127.0.0.1 - - [11/Aug/2021:17:11:15 +0800] "POST /fetch/189 HTTP/1.1" 200 10659 "http://localhost:3000/" "Mozilla/5.0 XXXXX"

日志内容记录了请求源、请求方法、请求接口、Http 版本、status code 、navigator 等。

Nginx 反向代理常用配置

打开 conf/nginx.conf 文件,如果只是做反向代理的话,大部分情况只需要配置 http 模块下的 server 即可,一般初始文件,只有一个 server,如果你需要 Nginx 同时开启不同的端口或域名,就需要写多个 server

server

一个 server 模块的配置如下:

 
ini

复制代码
server {
listen 80; # 端口号
server_name localhost; # server name 默认 localhost #access_log logs/host.access.log main; location / { # 访问路径匹配规则
root html;
index index.html index.htm;
} error_page 500 502 503 504 /50x.html; # 错误处理
location = /50x.html {
root html;
}
}

里面比较重要的是 location 模块,反向代理的主要工作也是配置 location

location

Location 配置项定义了一条访问 Nginx 服务某一路径时的匹配规则,location 后面紧跟的是匹配的路径,这个路径可以直接写绝对路径,可以写正则匹配:

 
bash

复制代码
location /api1 { # 当访问 http://localhost/api1 时命中
# ...
} location ~ ^/(api2/api3) { # 当访问 http://localhost/api2 和 http://localhost/api 3 时命中
# ...
}

proxy_pass

Location 里有多个配置项,其中一个是 proxy_pass ,意思是将当前命中的 Nginx 接口(例如:http://localhost/api )代理到其他 server 的接口,如下例子就是将 http://localhost/api 代理到 https://baidu.com/api

 
bash

复制代码
location /api {
proxy_pass https://baidu.com;
}

需要注意的是,在写 proxy_pass 不能随便在目标地址后加 /,如果你在地址末尾加了 / ,则最终代理是这样的:

 
bash

复制代码
location /api {
proxy_pass https://baidu.com/; # 将会被代理到 https://baidu.com/,后面没有 /api
}

不加 /,则最终代理是这样的,访问 Nginx 命中的 /api ,Nginx 也会自动帮你拼接上去:

 
bash

复制代码
location /api {
proxy_pass https://baidu.com; # 将会代理到 https://baidu.com/api
}

add_header

Location 配置中的 add_header选项,表示 Nginx 将在 response 中添加一些额外的响应头信息给客户端。众所周知,开启跨域支持是需要服务端配置 Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers 这些请求头的,那么既然有了 Nginx 做了中间层代理服务,就算 server 不给我们开启这些,我们完全也能够自给自足:

 
ini

复制代码
location /api {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Headers *;
add_header Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS";
proxy_pass https://baidu.com;
}

其中 * 代表通配符,意思是所有 origin 都允许跨域访问,这个当然是不安全的,但是本地开发就无所谓了,你也可以写自己指定的本地服务。

一般来说,如果请求过程中出现 40X 50X的错误,Nginx 将不会设置 Access-Control-Allow-Origin 继而导致跨域失败,所以需要在后面再加个 always 告诉 Nginx 不管怎样,都给我设置这个响应头。

OPTIONS 请求

其实,编写到这里,大多数的跨域场景,我们应该能很好的解决了,但是我的项目所需要解决的跨域问题,还不是这么简单。

首先,在开发的过程中,我发现对于有些 server ,连我发送的 OPTIONS 请求都过不去,查看 Nginx 日志才发现,Nginx 本身确实帮我将 OPTIONS 请求代理出去了,但是服务端不认这个 Request Method,它只认识 POST,反手给我一个 403。这就麻烦了,server 端不认这个请求,那么后续的 POST 根本没法发,这要咋办?

一番思考后,如果我直接让 Nginx 拦截 OPTIONS 直接响应 200 不就可以了嘛?

 
ini

复制代码
location /api {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Headers *;
add_header Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS";
if ($request_method = 'OPTIONS') {
return 200;
}
proxy_pass https://baidu.com;
}

重启,再试,成功。Nice,第一个问题解决。

proxy_set_header

这个配置项我的项目里并没有怎么用到,但是还是说一下,以便和 add_header 做出区分。

  • add_header 是当请求从 server 端回来时,Nginx 再往这个 response 里添加一些额外的 reponse header 然后发送给 客户端。
  • proxy_set_header 是当请求从客户端发出时,Nginx接收到 request 再往请求里添加一些额外的 request header 然后发送给 服务端。

常见的一些需要设置 proxy_set_header 的场景,比如说,有些 server 可能需要验证 Host,这个时候,就可以使用 proxy_set_header 伪造一个 Host 来骗过服务端。

proxy_hide_header

这个配置项用于隐藏部分服务端返回的 response header ,这个选项可以和 add_header 配合使用,来达到修改 server response header 的目的。比如有时候,我们做代理转发的时候可能会发现,服务端开启的 Access-Control-Allow-Origin 具体指定的列表里,没有我们发起请求的域,这种情况下跨域同样是无法绕过的;如果再次使用 add_header 重新添加一个新的 Access-Control-Allow-Origin 又会出现重复设置请求头的错误。这种情况下,我们就可以先使用 proxy_hide_header 来将原始 server response header 隐藏,再用 add_header 添加一个新的 response header,即可实现对 server response header 的修改。

 
ini

复制代码
location /api {
# 1. hide the Access-Control-Allow-Origin from the server response
proxy_hide_header Access-Control-Allow-Origin; # 2. add a new custom header that allows all * origin instead
add_header Access-Control-Allow-Origin *;
proxy_pass https://baidu.com;
}

跨域的 cookie 传输

Cookie 这个东西,由于事关安全性,所以是非常敏感的。Domain 不同,Path 不同,浏览器就不会让你访问到这个 Cookie,也不可能存储这个 Cookie ,更不可能让你随随便便地带着 Cookie 跨域名传输,所以要开启跨域情况下的 Cookie 传输,要求也是十分严苛的。

首先,我碰到的一个场景是:server 端需要记录访问 session ,以此来判定当前用户的登陆状态,后面的所有接口,都需要通过 session 来判断。然而当我成功调通登陆接口后,发现后续的所有接口都 Error 并且直接给我 302 到登陆界面,这就说明在服务端那里,我的状态根本就是没登陆,这是怎么回事?

一通排查,我发现是因为 server 端登陆成功后返回的一系列 session 我并没有做处理,所以它们的 Domain 都是 server 的 Domain。浏览器拿到后一看,你当前域是 localhost,你 Cookie 域是其他的域,cookie 不能给你。而后面的请求没有携带这些 session 信息,服务端自然不认得。

一通操作,终于找到了一个叫做 proxy_cookie_domain 的东西

Proxy_cookie_domain

这个选项是专门用来修改服务端返回回来的 cookie domain ,可以使用这个配置来将 cookie domain 手动修改后再返回,修改成本地开发的域,这样就能绕过浏览器的限制,成功将 cookie 保存。

 
ini

复制代码
location /api {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Headers *;
add_header Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS";
if ($request_method = 'OPTIONS') {
return 200;
}
proxy_cookie_domain ~\.?baidu.com $host;
proxy_pass https://baidu.com;
}

再试,可以,这把登陆完成后,打开控制台发现所有来自 server 的 cookie 都被保存下来了。接着请求其他接口,失败。

血压上来了卧槽(此处省略若干优美中国话)。

没有办法,继续解决。

继续找原因,发现是因为就算是本地访问了 Nginx 服务,其实也是跨域的,只不过我将 Nginx 开启了支持普通跨域,可是它不支持 cookie 的跨域传输啊。支持跨域传输 cookie 需要前后端同时支持。

WithCredentials

首先在前端这块,我用的是 axios,里面有一项安全认证的配置需要开启:withCredentials: true。将这个配置项打开,代表请求服务端进行跨域的 cookie 传输。

这个 withCredentials 配置项,应该是业界统一命名,命名差别不大,如果不是用的 axios 而且用的别的插件,应该都有这个配置项,写法不同而已,找一下应该能找到。

Access-Control-Allow-Credentials

搞完前端,继续搞 Nginx 里面需要加一个响应头信息:Access-Control-Allow-Credentials: true,代表服务端同意跨域传输凭证。

 
ini

复制代码
location /api {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Headers *;
add_header Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS";
add_header Access-Control-Allow-Credentials true;
if ($request_method = 'OPTIONS') {
return 200;
}
proxy_cookie_domain ~\.?baidu.com $host;
proxy_pass https://baidu.com;
}

再试,失败,我*****

再找原因,发现如果开启了 Access-Control-Allow-Credentials 那么 Access-Control-Allow-Origin 和 Access-Control-Allow-Headers 不能写成 * 这种通配符的形式,必须写明 origin 和 headers。

 
bash

复制代码
location /api {
add_header Access-Control-Allow-Origin http://localhost:3000 always;
add_header Access-Control-Allow-Headers "Accept,Accept-Encoding,Accept-Language,Connection,Content-Length,Content-Type,Host,Origin,Referer,User-Agent";
add_header Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS";
add_header Access-Control-Allow-Credentials true;
if ($request_method = 'OPTIONS') {
return 200;
}
proxy_cookie_domain ~\.?baidu.com $host;
proxy_pass https://baidu.com;
}

重启 Nginx,再试,成功了。

Proxy_cookie_path

在解决问题的过程中,我发现还有个叫做 proxy_cookie_path 的配置,虽然我没用到,但还是记录一下,这个配置的作用看名字就知道,应该和 proxy_cookie_domain 类似。

一般情况下,server 返回的 cookie 里,可能会写有 Path,如果浏览器的当前 Path 不满足这个 cookie path 的限定条件,cookie 同样是不可操作的。这个 proxy_cookie_path 配置就是为了将 server 返回的 cookie path 改写成自己需要的 cookie path 从而绕过浏览器的限制。

总结

开发两分钟,跨域两小时。

Nginx 的功能很强大,但学习成本也比较大,如果不是专业运维什么的,个人不建议投入大量时间专门学习,相反,如果在后面的工作中碰到问题,带着问题去学,应该会更有帮助。

最后给个 Nginx 配置项官方传送:Nginx 配置

和一篇不错的 nginx 的博客:Nginx 使用与异常处理

[转帖]Nginx 反向代理解决跨域问题的更多相关文章

  1. nginx反向代理解决跨域问题

    跨域:浏览器从一个域名的网页去请求另一个域名的资源时,域名.端口.协议任一不同,都是跨域 . 下表格为前后端分离的域名,技术信息:   域名 服务器 使用技术 前端 http://b.yynf.com ...

  2. 前端通过Nginx反向代理解决跨域问题

    在前面写的一篇文章SpringMVC 跨域,我们探讨了什么是跨域问题以及SpringMVC怎么解决跨域问题,解决方式主要有如下三种方式: JSONP CORS WebSocket 可是这几种方式都是基 ...

  3. 利用nginx 反向代理解决跨域问题

    说到nginx,不得不说真的很强大,也带来很多便利用于解决一些头疼的难题. 一般来说可以用来做:静态页面的服务器.静态文件缓存服务器.网站反向代理.负载均衡服务器等等,而且实现这一切,基本只需要改改那 ...

  4. Nginx 反向代理解决跨域问题分析

    当你遇到跨域问题,不要立刻就选择复制去尝试.请详细看完这篇文章再处理 .我相信它能帮到你. 分析前准备: 前端网站地址:http://localhost:8080 服务端网址:http://local ...

  5. nginx反向代理解决跨域问题,使本地调试更方便

    我们可能都会遇到一个这样的问题,线上环境是https://...,本地启动了项目,域名是localhost:8000等,本地想要访问线上的接口,直接在本地调试,却提示跨域,这个时候我们可以配置ngin ...

  6. nginx反向代理解决跨域

    nginx作为反向代理服务器,就是把http请求转发到另一个或者一些服务器上.通过把本地一个url前缀映射到要跨域访问的web服务器上,就可以实现跨域访问.对于浏览器来说,访问的就是同源服务器上的一个 ...

  7. vue前后分离项目部署(不同端口号,nginx反向代理解决跨域问题)

    #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #erro ...

  8. windows环境下 nginx+iis 反向代理解决跨域问题

    项目基本完成,是时候花点时间整理一下最近的姿势了 1 什么是跨域? 网上对于跨域的概念会有大篇幅的文章去解释,似乎有点玄乎,初学者很容易对这个概念产生恐惧,跨域其实很简单,其实只要知道一点,无法跨域访 ...

  9. nginx反向代理实现跨域请求

    nginx反向代理实现跨域请求 跨域请求可以通过JSONP实现,缺点是需要修改被请求的服务器端代码进行配合,稍显麻烦通过在自己服务器上配置nginx的反向代理,可以轻松实现跨域请求 思路 示例服务器A ...

  10. VUE线上通过nginx反向代理实现跨域

    1.NGINX反向代理实现跨域 VUE代码中配置参考上一篇文章 nginx配置,红色框线内: 代码: location /list { proxy_set_header X-Real-IP $remo ...

随机推荐

  1. win10 安装 AutoCAD

    有些人在 win10 系统下 安装 AutoCAD 会有些小问题,不要担心,根据下面这些图片就可以解决你的问题 答案很简单,就是安装.NET Framework3.5,这里提供一种安装方法供大家参考: ...

  2. 如何构建一个 NodeJS 影院微服务并使用 Docker 部署

    如何构建一个 NodeJS 影院微服务并使用 Docker 部署 前言 如何构建一个 NodeJS 影院微服务并使用 Docker 部署.在这个系列中,将构建一个 NodeJS 微服务,并使用 Doc ...

  3. GDAL从二进制数据流中构造数据集

    目录 1. 概述 2. 实现 1. 概述 参看<从二进制数据流中构造GDAL可以读取的图像数据>这篇文章.这个问题的内涵在于,处理图像时都会将其读取成宽X高X波段的三维数组的内存Buffe ...

  4. STM32CubeMX教程14 ADC - 多通道DMA转换

    1.准备材料 开发板(正点原子stm32f407探索者开发板V2.4) ST-LINK/V2驱动 STM32CubeMX软件(Version 6.10.0) keil µVision5 IDE(MDK ...

  5. Python图像处理丨带你掌握图像几何变换

    摘要:本篇文章主要讲解图像仿射变换和图像透视变换,通过Python调用OpenCV函数实. 本文分享自华为云社区<[Python图像处理] 十二.图像几何变换之图像仿射变换.图像透视变换和图像校 ...

  6. Error: Could not find or load main class org.elasticsearch.tools.java_version_checker.JavaVersionChecker

    把elasticsearch目录换到不属于root目录的其他目录就行了

  7. kubernetes实战(三十一):Prometheus监控Windows主机

    1. 基本说明 使用Prometheus监控Windows主机和Linux主机并无太大区别,都是使用社区的Exporter进行采集数据,之后暴露一个接口,可以让Prometheus采集到主机的数据. ...

  8. PPT 编辑顶点

    编辑顶点的N种玩法 针对特定的形状进行编辑 选中形状 -> 右键 -> 编辑顶点 如果[编辑顶点]是灰色的,需要上网下一个 office clean touris,清理一下 合并形状:多个 ...

  9. uniapp picker组件实现二级联动

    https://blog.csdn.net/hxh_csdn/article/details/111504951 https://www.cnblogs.com/jstll/p/14149600.ht ...

  10. 单线程 Redis 如此快的 4 个原因

    本文翻译自国外论坛 medium,原文地址:https://levelup.gitconnected.com/4-reasons-why-single-threaded-redis-is-so-fas ...