Nginx解决跨域问题

先来说一下什么是同源策略

同源(域名、协议、端口相同)策略是一种约定,是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,浏览器的正常功能将受到影响。

什么是跨域?

跨域就是跨域名,跨端口,跨协议(非同源策略)。

跨域分类

简单说,跨域分为 简单跨域复杂跨域

简单跨域:不会发送OPTIONS请求。

复杂跨域:会发送一个预检查OPTIONS请求。


复杂跨域的条件是:

    ①、非GET、HEAD、POST请求。

    ②、POST请求的Content-Type不是application/x-www-form-urlencoded, multipart/form-data, 或text/plain。

    ③、添加了自定义header,例如Token。

跨域请求浏览器会在Headers中添加Origin,通常情况下不允许用户修改其值。

Nginx解决跨域问题

跨域是前后端分离开发中非常常见的问题。无论用什么编程语言,现在都已经很难离开Nginx。因此直接在Nginx中处理跨域问题有得天独厚的优势。当出现跨域问题的时候,只需要给Nginx服务器配置响应的header参数即可。


只需要在Nginx的配置文件中配置以下参数:

location / {  
    add_header Access-Control-Allow-Origin *;
    
    add_header 'Access-Control-Allow-Credentials' 'true'; # 是否允许后续请求携带cookies,该值只能是true,否则不返回。如果上面的Access-Control-Allow-Origin设置的是* 而你又需要cookie信息,则 必须设置这行。
    
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    
    add_header Access-Control-Allow-Headers *;

    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

上面的配置代码即可解决跨域问题了,不想深入研究的,看到这里就可以了=-=

解释

1、Access-Control-Allow-Origin

服务器默认是不被允许跨域的。给Nginx配置Access-Control-Allow-Origin * 后,表示服务器可以接受所有的请求源(Origin),即接受所有跨域的请求。也就是说,表示接受任意域名的请求。上面我们这里设置的是* 这是最简单粗暴的方式,但是服务器出于安全考虑,肯定不会这么干,而且,如果是*的话,游览器将不会发送cookies数据(如果需要携带cookies数据,则需要设置 'Access-Control-Allow-Credentials:true')。


所以Access-Control-Allow-Origin一般都是设置为 指定域(也就是指定 某一个url来请求我服务器)的方式。指定域 设置的方式如下:


add_header Access-Control-Allow-Origin 'http://www.test.com';  注意:只能指定一个域

2、Access-Control-Allow-Headers 是为了防止出现以下错误:

Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.


这个错误表示当前请求Content-Type的值不被支持。其实是我们发起了"application/json"的类型请求导致的。这里涉及到一个概念:预检请求(preflight request)。请看下面"预检请求"的介绍。

3、Access-Control-Allow-Methods 是为了防止出现以下错误:

Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

4、给OPTIONS 添加 204的返回,是为了处理在发送POST请求时Nginx依然拒绝访问的错误

发送"预检请求"时,需要用到方法OPTIONS,所以服务器需要允许该方法。

预检请求(preflight request)

其实上面的配置涉及到了一个W3C标准:CROS,全称是跨域资源共享 (Cross-origin resource sharing),它的提出就是为了解决跨域请求的。


跨域资源共享(CORS)标准新增了一组HTTP 首部字段,允许服务器声明哪些源站有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。


其实Content-Type字段的类型为application/json的请求 就是上面所说的搭配某些 MIME 类型的 POST 请求,CORS规定,Content-Type不属于以下MIME类型的,都属于预检请求:

application/x-www-form-urlencoded
multipart/form-data
text/plain

POST请求中的Content-Type不是上面这三种的其中一种的话,都属于预检请求。(上面也有提到过,就是 复杂跨域的条件中的第②步)


所以 application/json的请求 会在正式通信之前,增加一次"预检"请求,这次"预检"请求会带上头部信息 Access-Control-Request-Headers: Content-Type:

OPTIONS /api/test HTTP/1.1
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
... 省略了一些


服务器回应时,返回的头部信息如果不包含Access-Control-Allow-Headers: Content-Type则表示不接受非默认的的Content-Type。即出现以下错误:


Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

尾声

解决跨域的解决方案有很多种,最常见也是最传统的解决方式是使用JSONP的方式。CORS与JSONP的使用目的相同,但是比JSONP更强大。


JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。


参考:阮一峰【跨域资源共享 CORS 详解】



2024-06-12更新  nginx配置后 依然出现跨域的问题(nginx配置跨域没有生效 的解决方式)

nginx版本:nginx version: nginx/1.24.0


跨域可以配置在location段 也可以配置在server段(如果配置在server下 则当前server下的location都生效)或者其它段

首先 确认原来的nginx的跨域配置是没问题的,后来追踪资料 发现 add_header 不是所有返回都会追加,只限特定状态码的返回才有效,如果想所有返回都生效需要加上 always选项参数。具体来看下nginx官方的解释:


官方文档地址:http://nginx.org/en/docs/http/ngx_http_headers_module.html

Syntax: add_header name value [always]; 
Default: — 
Context: http, server, location, if in location

Adds the specified field to a response header provided that the response code equals 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), or 308 (1.13.0).  Parameter value can contain variables.

在不添加 always 的情况下,add_header只对响应码为200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), 308 (1.13.0)这些生效(括号内是nginx版本)。也就是说当服务端返回响应异常,响应码不是上述之一的话,即使nginx有配跨域头信息,浏览器仍然会显示跨域错误。原因就是因为nginx对异常响应码添加add_header无效。


实际工作中往往前端需要捕获服务端异常响应,这时在nginx跨域add_header上加上always,使nginx对任何响应码都生效。


If the always parameter is specified (1.7.5), the header field will be added regardless of the response code.

如果指定了always参数(1.7.5),无论响应码是什么,都会添加报头字段。



知其然 知其所以然。 于是修改nginx跨域相关的配置加上always参数。(本文这里直接加在了server段下) 具体配置如下:

server
{
    listen 80;
    server_name aaa.bbb.com;
    index index.php index.html index.htm default.php default.htm default.html;
    root /www/wwwroot/aaa/bbb/public;

    #ERROR-PAGE-START  错误页配置,可以注释、删除或修改
    #error_page 404 /404.html;
    #error_page 502 /502.html;
    #ERROR-PAGE-END

    #PHP-INFO-START  PHP引用配置,可以注释或修改
    include enable-php-80.conf;
    #PHP-INFO-END

    # 跨域配置
    # add_header 'Access-Control-Allow-Origin' '*' always; # 如果这里设置* 就不能在Access-Control-Allow-Credentials中设置true 这俩个是冲突的 因为*号表示允许任何源 而浏览器不允许在跨域请求中携带凭据(如cookie、HTTP认证及客户端SSL证明等)到非同源服务器 除非Access-Control-Allow-Origin明确指定了请求的源。 如果你需要支持跨域并携带凭据 则需要明确指定允许的源 而不是使用*
    
    add_header 'Access-Control-Allow-Origin' $http_origin always; # 在nginx中 $http_origin是一个变量 它表示http请求头中的Origin字段。例:浏览器从 https://abc.com 发起一个请求到 https://api.xyz.com 且浏览器遵循cors策略 那么请求头中会包含一个Origin: https://abc.com 的字段。此时 在nginx配置中使用 $http_origin变量 就可以获取到 https://abc.com 这个值。
    
    add_header 'Access-Control-Request-Method' 'GET,POST,OPTIONS,DELETE,PUT' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
    add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
    
    
    # 针对OPTIONS请求做了特殊处理。在跨域请求中 通常情况下 浏览器首先会先发送一个OPTIONS请求 来检查服务器是否允许实际的跨域请求。这里配置的意思是:当检测到请求方法为OPTIONS时 直接返回204状态码 不进行其他处理。这是一种简短的响应 它告诉浏览器允许跨域 而且无需发送实际请求的内容 这样既实现了跨域的预检要求 又避免了对实际资源的无效访问 从而不会影响其他类型的请求处理流程。
    
    # 当nginx遇到OPTIONS请求时 这里使用了一个if块来特别处理它 并返回204状态码 这是为了响应浏览器的预检请求 告诉浏览器:“我知道你要做什么,并且我允许你这么做,所以你现在可以发送实际的请求了。
    location / {
            if ($request_method = 'OPTIONS') {
                return 204;
            }
       }
       
 
      #  因为跨域配置放在了server块下 所以该跨域配置对任何请求都有效(无论是GET、POST、PUT或是DELETE等) 所以这些请求都会受到上面设置的跨域响应头的影响(OPTIONS请求除外 因为单独做了处理 直接返回了204状态码)
      
 
}

重启nginx 然后刷新页面,然后你会神奇的发现,跨域报错消失了。这样 跨域问题 我们就在nginx层面进行解决了。



声明:禁止任何非法用途使用,凡因违规使用而引起的任何法律纠纷,本站概不负责。

小周博客
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

精彩评论

全部回复 0人评论 7,777人参与

loading