Nginx 反向代理解决跨域问题多种情况分析


Posted in Servers onJanuary 18, 2022

当你遇到跨域问题,不要立刻就选择复制去尝试。请详细看完这篇文章再处理 。我相信它能帮到你。

分析前准备:

前端网站地址:http://localhost:8080

服务端网址:http://localhost:59200

首先保证服务端是没有处理跨域的,其次,先用postman测试服务端接口是正常的

Nginx 反向代理解决跨域问题多种情况分析

当网站8080去访问服务端接口时,就产生了跨域问题,那么如何解决?接下来我把跨域遇到的各种情况都列举出来并通过nginx代理的方式解决(后台也是一样的,只要你理解的原理)。

跨域主要涉及4个响应头:

Access-Control-Allow-Origin 用于设置允许跨域请求源地址 (预检请求和正式请求在跨域时候都会验证)

Access-Control-Allow-Headers跨域允许携带的特殊头信息字段(只在预检请求验证)

Access-Control-Allow-Methods跨域允许的请求方法或者说HTTP动词(只在预检请求验证)

Access-Control-Allow-Credentials 是否允许跨域使用cookies,如果要跨域使用cookies,可以添加上此请求响应头,值设为true(设置或者不设置,都不会影响请求发送,只会影响在跨域时候是否要携带cookies,但是如果设置,预检请求和正式请求都需要设置)。不过不建议跨域使用(项目中用到过,不过不稳定,有些浏览器带不过去),除非必要,因为有很多方案可以代替。

网上很多文章都是告诉你直接Nginx添加这几个响应头信息就能解决跨域,当然大部分情况是能解决,但是我相信还是有很多情况,明明配置上了,也同样会报跨域问题。

什么是预检请求?:当发生跨域条件时候,览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。如下图

Nginx 反向代理解决跨域问题多种情况分析

开始动手模拟:

Nginx代理端口:22222 ,配置如下

server {
        listen       22222;
        server_name  localhost;
        location  / {
            proxy_pass  http://localhost:59200;
        }
}

测试代理是否成功,通过Nginx代理端口2222再次访问接口,可以看到如下图通过代理后接口也是能正常访问

Nginx 反向代理解决跨域问题多种情况分析

接下来开始用网站8080访问Nginx代理后的接口地址,报错情况如下↓↓↓

情况1:

Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Nginx 反向代理解决跨域问题多种情况分析

通过错误信息可以很清晰的定位到错误(注意看标红部分)priflight说明是个预请求,CORS 机制跨域会首先进行 preflight(一个 OPTIONS 请求), 该请求成功后才会发送真正的请求。 这一设计旨在确保服务器对 CORS 标准知情,以保护不支持 CORS 的旧服务器

通过错误信息,我们可以得到是预检请求的请求响应头缺少了Access-Control-Allow-Origin,错哪里,我们改哪里就好了。修改Nginx配置信息如下(红色部分为添加部分),缺什么就补什么,很简单明了

server {
        listen       22222;
        server_name  localhost;
        location  / {
           add_header Access-Control-Allow-Origin 'http://localhost:8080';
           proxy_pass  http://localhost:59200; 
        }
    }

哈哈,当满怀欢喜的以为能解决后,发现还是报了同样的问题

Nginx 反向代理解决跨域问题多种情况分析

不过我们的配置没什么问题,问题在Nginx,下图链接http://nginx.org/en/docs/http/ngx_http_headers_module.html

Nginx 反向代理解决跨域问题多种情况分析

add_header 指令用于添加返回头字段,当且仅当状态码为图中列出的那些时有效。如果想要每次响应信息都携带头字段信息,需要在最后添加always(经我测试,只有Access-Control-Allow-Origin这个头信息需要加always,其他的不加always也会携带回来),那我们加上试试

server {
        listen       22222;
        server_name  localhost;
        location  / {
           add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
           proxy_pass  http://localhost:59200; 
        }
    }

修改了配置后,发现生效了,当然不是跨域就解决了,是上面这个问题已经解决了,因为报错内容已经变了

情况2:

Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

Nginx 反向代理解决跨域问题多种情况分析

通过报错信息提示可以得知,是跨域浏览器默认行为的预请求(option请求)没有收到ok状态码,此时再修改配置文件,当请求为option请求时候,给浏览器返回一个状态码(一般是204)

server {
        listen       22222;
        server_name  localhost;
        location  / {
           add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
           if ($request_method = 'OPTIONS') {
                return 204;
           }
           proxy_pass  http://localhost:59200; 
        }
    }

当配置完后,发现报错信息变了

情况3:

Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.

Nginx 反向代理解决跨域问题多种情况分析

意思就是预请求响应头Access-Control-Allow-Headers中缺少头信息authorization(各种情况会不一样,在发生跨域后,在自定义添加的头信息是不允许的,需要添加到请求响应头Access-Control-Allow-Headers中,以便浏览器知道此头信息的携带是服务器承认合法的,我这里携带的是authorization,其他的可能是token之类的,缺什么加什么),知道了问题所在,然后修改配置文件,添加对应缺少的部分,再试试

server {
        listen       22222;
        server_name  localhost;
        location  / {
           add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
           if ($request_method = 'OPTIONS') {
               add_header Access-Control-Allow-Headers 'authorization'; #为什么写在if里面而不是接着Access-Control-Allow-Origin往下写?因为这里只有预检请求才会检查
               return 204; 
          } 
        proxy_pass http://localhost:59200; 
    }
    }

此时发现报错问题又回到了情况1

Nginx 反向代理解决跨域问题多种情况分析

经测试验证,只要if ($request_method = 'OPTIONS') 里面写了add_header ,当为预检请求时外部配置的都会失效,为什么? ↓↓。

官方文档是这样说的:

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

意思就是当前层级无 add_header 指令时,则继承上一层级的add_header。相反的若当前层级有了add_header,就应该无法继承上一层的add_header。

Nginx 反向代理解决跨域问题多种情况分析

配置修改如下:

server {
        listen       22222;
        server_name  localhost;
        location  / {
            add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
            if ($request_method = 'OPTIONS') {
                add_header Access-Control-Allow-Origin 'http://localhost:8080';
                add_header Access-Control-Allow-Headers 'authorization';
                return 204;
            }
            proxy_pass  http://localhost:59200; 
        }
    }

此时改完发现跨域问题已经解决了,

Nginx 反向代理解决跨域问题多种情况分析

不过以上虽然解决了跨域问题,但是考虑后期可能Nginx版本更新,不知道这个规则会不会被修改,考虑到这样的写法可能会携带上两个Access-Control-Allow-Origin ,这种情况也是不允许的,下面会说到。所以配置适当修改如下:

server {
        listen       22222;
        server_name  localhost;
        location  / {
            if ($request_method = 'OPTIONS') {
                add_header Access-Control-Allow-Origin 'http://localhost:8080';
                add_header Access-Control-Allow-Headers 'authorization';
                return 204;
            }
            if ($request_method != 'OPTIONS') {
                add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
            }
            proxy_pass  http://localhost:59200; 
        }
    }

还没完,继续聊 ↓↓

情况4:

比较早期的API可能只用到了POST和GET请求,而Access-Control-Allow-Methods这个请求响应头跨域默认只支持POST和GET,当出现其他请求类型时候,同样会出现跨域异常。

比如,我这里将请求的API接口请求方式从原来的GET改成PUT,在发起一次试试。在控制台上会抛出错误:

Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.

Nginx 反向代理解决跨域问题多种情况分析

报错内容也讲的很清楚,在这个预请求中,PUT方法是不允许在跨域中使用的,我们需要改下Access-Control-Allow-Methods的配置(缺什么加上么,这里我只加了PUT,可以自己加全一点),让浏览器知道服务端是允许的

server {
    listen 22222;
    server_name localhost;
    location / {
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080';
            add_header Access-Control-Allow-Headers 'content-type,authorization';
            add_header Access-Control-Allow-Methods 'PUT';#为这么只加在这个if中,不再下面的if也加上?因为这里只有预检请求会校验,当然你加上也没事。
            return 204;
        }
        if ($request_method != 'OPTIONS') {
            add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
        }
        proxy_pass http://localhost:59200;
    }
}

这里注意一下,改成PUT类型后,Access-Control-Allow-Headers请求响应头又会自动校验content-type这个请求头,和情况3是一样的,缺啥补啥就行了。如果不加上content-type,则会报如下错误。(想简单的话,Access-Control-Allow-Headers和Access-Control-Allow-Methods可以设置为* ,表示全都匹配。但是Access-Control-Allow-Origin就不建议设置成 * 了,为了安全考虑,限制域名是很有必要的。)

Nginx 反向代理解决跨域问题多种情况分析

都加上后,问题就解决了,这里报405是我服务端这个接口只开放了GET,没有开放PUT,而此刻我将此接口用PUT方法去请求,所以接口会返回这个状态码。

Nginx 反向代理解决跨域问题多种情况分析

情况5:

最后再说一种情况,就是后端处理了跨域,就不需要自己在处理了(这里吐槽下,某些后端工程师自己改服务端代码解决跨域,但是又不理解其中原理,网上随便找段代码黏贴,导致响应信息可能处理不完全,如method没添加全,headers没加到点上,自己用的那个可能复制过来的并不包含实际项目所用到的,没有添加options请求返回状态码等,导致Nginx再用通用的配置就会可能报以下异常)

Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, http://localhost:8080', but only one is allowed.

Nginx 反向代理解决跨域问题多种情况分析

Nginx 反向代理解决跨域问题多种情况分析

意思就是此刻Access-Control-Allow-Origin请求响应头返回了多个,而只允许有一个,这种情况当然修改配置去掉Access-Control-Allow-Origin这个配置就可以了,不过遇到这种情况,建议Nginx配置和服务端自己解决跨域只选其一。(这里注意如果按我上面的写法,if $request_method = 'OPTIONS' 这个里面的Access-Control-Allow-Origin可不能删除,删除!='OPTIONS'里面的就好了,因为这里如果是预检请求直接就ruturn了,请求不会再转发到59200服务,如果也删除了,就会报和情况1一样的错误。所以为什么说要不服务端代码层面解决跨域,要不就Nginx代理解决,不要混着搞,不然不明白原理的人,网上找一段代码贴就很可能解决不了问题)

↓↓↓↓↓

再贴一份完整配置(*号根据自己‘喜好’填写):

server {
        listen       22222;
        server_name  localhost;
        location  / {
            if ($request_method = 'OPTIONS') {
                add_header Access-Control-Allow-Origin 'http://localhost:8080';
                add_header Access-Control-Allow-Headers '*';
                add_header Access-Control-Allow-Methods '*';
                add_header Access-Control-Allow-Credentials 'true';
                return 204;
            }
            if ($request_method != 'OPTIONS') {
                add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
                add_header Access-Control-Allow-Credentials 'true';
            }
            proxy_pass  http://localhost:59200; 
        }
    }

或者:

server {
        listen       22222;
        server_name  localhost;
        location  / {
            add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
            add_header Access-Control-Allow-Headers '*';
            add_header Access-Control-Allow-Methods '*';
            add_header Access-Control-Allow-Credentials 'true';
            if ($request_method = 'OPTIONS') {
                return 204;
            }
            proxy_pass  http://localhost:59200; 
        }
    }

最后,这是一篇解决跨域遇到问题解决问题的过程,如果认真看完了,我相信应该都能很容易的理解,并且在实际使用中自己解决该问题,希望能帮助到大家,以上内容都是自己理解自己测试码出来的,如有理解不对的地方,望大家指正。

到此这篇关于Nginx 反向代理解决跨域问题分析的文章就介绍到这了,更多相关Nginx 反向代理解决跨域内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Servers 相关文章推荐
nginx基于域名,端口,不同IP的虚拟主机设置的实现
Mar 31 Servers
解析在浏览器地址栏输入一个URL后发生了什么
Jun 21 Servers
使用goaccess分析nginx日志的详细方法
Jul 09 Servers
nginx从安装到配置详细说明(安装,安全配置,防盗链,动静分离,配置 HTTPS,性能优化)
Feb 12 Servers
解决xampp安装后Apache无法启动
Mar 21 Servers
配置Kubernetes外网访问集群
Mar 31 Servers
Kubernetes控制节点的部署
Apr 01 Servers
Windows Server 2012配置DNS服务器的方法
Apr 29 Servers
Nginx HTTP跳转至HTTPS
May 15 Servers
win10+RTX3050ti+TensorFlow+cudn+cudnn配置深度学习环境的方法
Jun 25 Servers
ssh服务器拒绝了密码 请再试一次已解决(亲测有效)
Aug 14 Servers
CentOS7设置ssh服务以及端口修改方式
Dec 24 Servers
详解nginx location指令
Jan 18 #Servers
图文详解nginx日志切割的实现
Jan 18 #Servers
Nginx防盗链与服务优化配置的全过程
Jan 18 #Servers
使用 Apache 反向代理的设置技巧
NGINX 权限控制文件预览和下载的实现原理
Nginx虚拟主机的搭建的实现步骤
Jan 18 #Servers
Nginx下SSL证书安装部署步骤介绍
Dec 06 #Servers
You might like
评分9.0以上的动画电影,剧情除了经典还很燃
2020/03/04 日漫
加速XP搜索功能堪比vista
2007/03/22 PHP
PHP 文件类型判断代码
2009/03/13 PHP
解析php中eclipse 用空格替换 tab键
2013/06/24 PHP
php时间戳格式化显示友好的时间函数分享
2014/10/21 PHP
关于PHP中字符串与多进制转换函数的实例代码
2016/11/03 PHP
Yii2实现多域名跨域同步登录退出
2017/02/04 PHP
JavaScript 面向对象编程(2) 定义类
2010/05/18 Javascript
jQuery 中$(this).index与$.each的使用指南
2014/11/20 Javascript
jQuery+HTML5实现手机摇一摇换衣特效
2015/06/05 Javascript
元素绑定click点击事件方法
2015/06/08 Javascript
在AngularJS中如何使用谷歌地图把当前位置显示出来
2016/01/25 Javascript
老生常谈JavaScript中的this关键字
2016/10/01 Javascript
不使用script导入js文件的几种方法
2016/10/27 Javascript
js Canvas实现的日历时钟案例分享
2016/12/25 Javascript
vue2滚动条加载更多数据实现代码
2017/01/10 Javascript
基于JavaScript+HTML5 实现打地鼠小游戏逻辑流程图文详解(附完整代码)
2017/11/02 Javascript
Node.js笔记之process模块解读
2018/05/31 Javascript
js轮播图之旋转木马效果
2020/10/13 Javascript
python验证码识别的示例代码
2017/09/21 Python
Python探索之Metaclass初步了解
2017/10/28 Python
浅析PHP与Python进行数据交互
2018/05/15 Python
virtualenv 指定 python 解释器的版本方法
2018/10/25 Python
Python实现图片识别加翻译功能
2019/12/26 Python
Python如何将模块打包并发布
2020/08/30 Python
css3通过scale()、rotate()实现放大、旋转
2020/03/19 HTML / CSS
如何定义一个可复用的服务
2014/09/30 面试题
自我评价范文
2013/12/22 职场文书
《小小雨点》教学反思
2014/02/18 职场文书
商铺门前三包责任书
2014/07/25 职场文书
肖申克救赎观后感
2015/06/02 职场文书
2016年主题党日活动总结
2016/04/05 职场文书
导游词之藏龙百瀑景区
2019/12/30 职场文书
试用1103暨1103、1101同门大比武 [ DAIWEI ]
2022/04/05 无线电
解决MySQL报“too many connections“错误
2022/04/19 MySQL
JS前端canvas交互实现拖拽旋转及缩放示例
2022/08/05 Javascript