Nginx 解决跨域

2023-11-17

项目准备

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

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

确认服务端是没有处理跨域的,先用postman测试服务端接口是正常的

在这里插入图片描述

当前端网站8080去访问服务端接口时,就产生了跨域问题,那么如何解决?接下来我把跨域遇到的各种情况都列举出来并通过nginx代理的方式解决

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

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

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

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

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

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

图片

开始动手模拟

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

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

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

图片

接下来开始用前端网站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.

图片

通过错误信息可以很清晰的定位到错误(注意看标红部分)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:8081;
    }
}

以为能解决后,发现还是报了同样的问题

图片

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

图片

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:8081;
    }
}

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

情况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.

图片

通过报错信息提示可以得知,是跨域浏览器默认行为的预请求(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:8081;
    }
}

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

情况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.

图片

意思就是预请求响应头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:8081;
}
}

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

图片

经测试验证,只要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。

图片

配置修改如下:

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:8081;
    }
}

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

图片

不过以上虽然解决了跨域问题,但是考虑后期可能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:8081;
    }
}

情况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.

图片

报错内容也讲的很清楚,在这个预请求中,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:8081;
    }
}

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

图片

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

图片

情况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.

在这里插入图片描述

意思就是此刻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:8081;
    }
}

或者:


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;
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Nginx 解决跨域 的相关文章

随机推荐

  • 图解PROFINET——PROFINET IO设备类型

    PROFINET IO的设备类型分为控制器 监视器 设备 DP Master System PROFINET IO System DP Master Class 1 PROFINET IO Controller DP Slave PROFI
  • webpack打包多个文件

    webpack config js const pathlib require path module exports entry index pages index pageA pages pageA output path pathli
  • python-selenium-框架定位的处理frame

    Frame框架是Html框架结构中一种布局格式 共有三种形式 frameset frame iframe frame是整个页面的框架 iframe是内嵌的网页元素 也可以说是内嵌的框架 页面比较复杂 页面套页面 定位第几层窗口 找元素时 要
  • C语言—总结1—运算符,语句,数据类型

    一 C语言的基本结构 1 include
  • 微软北大联合提出换脸 AI 和脸部伪造检测器,演绎现实版「矛与盾」?

    2020 01 07 18 28 导语 一个致力于造假 一个专注于打假 雷锋网 AI 开发者按 近日 微软研究院与北京大学的研究小组共同提出了一种全新的 AI 换脸框架 FaceShifter 以及一种检测伪造人脸图像的方法 FaceX R
  • 渗透Metasploitable主机

    攻击机 kali 靶机 Metasploitable2 一个特制的ubuntu操作系统 设计初衷作为安全工具测试和演示常见漏洞攻击的环境 默认开放高危端口如21 22 23 445 6000等等 而且具有很多未打补丁的高危漏洞 Metasp
  • 直接告诉你EndNote怎么用!

    如何用 endnote 导入新文献做自己的文献数据库 如何修改导入以后的英文的具体内容 例如修改数据库中的作者姓名 发表时间 文章页码 种属名的斜体 如何给自己的文章插入文献 EndNote安装包下载 EndNoteX9 先下载micros
  • Verilog的奇技淫巧[更新中]

    1 Verilog常用的数据选择语句vect a b 或vect a b 转载自 MDY常用的数据选择语句Verilog明德扬论坛 Powered by Discuz vect为变量名字 a为起始位置 加号或者减号代表着升序或者降序 b是进
  • Python获取MP3文件的ID3标签信息

    在Python中 你可以使用第三方库来获取MP3文件的ID3标签信息 ID3标签是嵌入在MP3文件中的元数据 它包含了有关音频文件的信息 如歌曲名称 艺术家 专辑 年份等 一种常用的Python库是mutagen 它提供了一个简单而强大的接
  • 如何用matlab计算自相关,Matlab计算自相关和互相关

    1 x t sinc 10t y t rect 10t 利用Matlab求 x t y t 的自相关函数及互相关函数 rect x abs s lt 0 5 定义rect 矩形脉冲 T 10 dt 0 001 t T 2 dt T 2 x
  • QT ToolBar上工具按钮隐藏

    之前再toolBar上添加工具时 用的QPushButton 然后再addWidget就可以了 但是当想要隐藏掉 调用hide和setVisible时 发现没反应 这块也没有细究 只寻找答案 解决办法 1 使用QToolButton和QAc
  • 学习笔记:运算放大器(OPA)超详细讲解(内含运用电路仿真)

    一运算放大器的简介 放大器 简称 运放 是具有很高放大倍数的电路单元 运算放大器是一个内含多级放大电路的电子集成电路 分别是输入级 中间级 发大级还有偏置电路 红色绿色蓝色分别是输入级 中间级和输出级 输入级采用具有很强零点漂移抑制能力的差
  • CSDN超级实习生计划——大厂实习直通车,年薪最高可达30W。2022正式开启~

    一 CSDN超级实习生计划是什么 2022届高校毕业生规模预计1076万人 同比增加167万人 这是高校毕业生规模首次超过千万 也是近几年增长人数最多的一年 1076万大学生就业的压力急剧暴增 也将迎来最难就业季 超级实习生计划是CSDN重
  • 一天1个机器学习知识点(一)

    陆陆续续整理的机器学习的知识点 资料大多数来自网上 不做盈利目的 如果侵权请告知即删 如果文章中有错误的地方还请各位同学指正 一起学习 一起进步 每天都在更新中 记得收藏 每天进步一点点 一天1个机器学习知识点 一 决策树 有无监督学习 S
  • ajax中的application/x-www-form-urlencoded中的使用

    一 HTTP上传的基本知识 在Form元素的语法中 EncType表明提交数据的格式 用 Enctype 属性指定将数据回发到服务器时浏览器使用的编码类型 下边是说明 application x www form urlencoded 窗体
  • MSP430 F5529的按钮控制led灯亮灭程序代码——按一下亮一下,再按一下暗

    2019 6 27 MP430F5529 电子工艺实习实验1 作业1 按下按键 LED亮 再按一次 LED灭 设置P8 1输出灯 P1 2输入按钮 P1 2下降沿 1 0 中断 中断标识为0 给按钮设置上拉电阻让其的高电位更加稳定 设置这两
  • 详解Java基础中注释添加的位置以及原则

    一 添加注释的位置 1 类 接口 这一部分注释是必须的 在这里 我们需要使用javadoc注释 需要标明 创建者 创建时间 版本 以及该类的作用 2 方法 在方法中 我们需要对入参 出参 以及返回值 均要标明 3 常量 对常量 我们需要使用
  • error LNK2005: _DllMain@12 already defined in MSVCRTD.lib

    本文主要分析和解决编译链接时产生的 LNK2005 错误 错误信息 mfcs90ud lib dllmodul obj error LNK2005 DllMain 12 already defined in MSVCRTD lib dllm
  • System.currentTimeMillis()

    System currentTimeMillis 计算方式与时间的单位转换 一 时间的单位转换 1秒 1000毫秒 ms 1毫秒 1 1 000秒 s 1秒 1 000 000 微秒 s 1微秒 1 1 000 000秒 s 1秒 1 00
  • Nginx 解决跨域

    项目准备 前端网站地址 http localhost 8080 服务端网址 http localhost 8081 确认服务端是没有处理跨域的 先用postman测试服务端接口是正常的 当前端网站8080去访问服务端接口时 就产生了跨域问题