nginx解决跨域的实际应用

随着前后端分离成为主流,跨域请求已成为每个 Web 开发者必须面对的问题。传统的解决跨域的 JSONP 技术局限昭著,为此市面上出现了无数的跨域解决方案。本文将具体介绍如何利用反向代理服务器 Nginx 来简单、优雅地实现跨域。

背景

跨域请求由浏览器的“同源策略”造成,是必然会遇到的问题。目前主流的解决方案是在服务器端进行 CORS 配置,允许指定域的跨域请求。但是这样需要服务端的配置配合,一旦后端无法修改,前端就无能为力了。
使用 Nginx 作为反向代理服务器,可以轻松地在前端工程中解决跨域问题,无需后端进行任何配置,只需要在前端请求地址进行简单改造,通过 Nginx 转发请求即可实现跨域。这种方式成本低、可控性好,非常适合前后端分离的项目。
本文将具体介绍 Nginx 的反向代理配置,以及如何在前端代码中使用,从而完美解决跨域请求,使前后端开发真正解耦合。

Nginx配置

在下载安装了Nginx后,以mac为例,配置文件的目录如下:

nginx.conf是主配置文件,里面会做一些的全局的配置,如错误日志的目录等等。里面会引用servers目录下的其它配置文件,这里一般放一些具体的配置。

http区块下可以包含多个server区块,各个server监听不同的域名和端口号。

#user  nobody; # worker进程的用户
worker_processes  1; # worker进程的数量

error_log /var/log/nginx/error.log; # 所有的错误写入的文件,在其它区块里还可以进一步设置

events {
    worker_connections  1024; # worker进程能够接受并发连接的最大数
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 9;
    gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/javascript application/json;
    gzip_disable "MSIE [1-6]\.";
    gzip_vary on;

    server {
        listen       48635;
        server_name  localhost;

        # /匹配到的路由,会在root指定的html文件夹查找index.html或index.htm文件
        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    include servers/*;
}

location

location命令只能在server区块下定义,指定了路由匹配规则,语法如下:

location [修改符] uri {...}
location @name {...} # 具名的路由

location不加修饰符时是普通的根据前缀匹配,加修饰符时根据修饰符的不同可分为精确匹配和正则匹配。

location的修饰符有四种:

  • = 精确匹配
  • ~ 正则匹配,有大小写
  • ~* 正则匹配,不区分大小写
  • ^~ 优先于正则的普通前缀匹配

举个例子:

location = /api 精确匹配/api的路由

location ~ /api 匹配/api开头的路由,~加*可以匹配/Api这样的路由

loicaton ^~ /api是前缀匹配,但比正则匹配的优先级更高。

整体的优先级顺序从高到低是:

精确匹配(=) > 高优先级前缀匹配(^~) > 正则匹配(~ ~*) > 普通前缀匹配

try_files

try_files可用在server下,但经常是用在location里。常用的用法是根据给的参数顺序进行尝试,将匹配到的处理传递给一个具名的location。

location / {
    try_files @uri @uri/ @mongrel;
}
location @mongrel {
    proxy_pass http://apiserver;
}

反向代理

相信很多人已经了解了正向代理与反向代理的不同,这里做个简单的比较。

正向与反向

不管是正向还是反向,代理是介于用户与目标服务器之间的桥梁。

"正向代理"主要解决客户端IP访问受限(如:VPN)和网络安全性(如:防火墙)的问题,典型的场景就是梯子。

"反向代理"(Reverse Proxy)主要解决服务器稳定性和资源利用的问题(加强网络数据处理能力、提高网络的灵活性和可用性), 典型的场景是负载均衡。

解决跨域需要的配置

出现跨域问题通常是因为请求了非同源的接口,为了解决跨域,我们使用Nginx作为反向代理,终结来自客户端的请求,并向上游服务器打开一个新的请求。代理到上游服务器的配置中,最重要的是proxy_pass指令。

proxy_pass

该指令接收一个参数,表示url请求会被转换。例如将uri请求代理到apserver上的/newuri。

location /uri {
	proxy_pass http://apiserver/newuri;
}

proxy_pass转发有两个例外情况:location 定义的正则表达式不会发生转换;location内有rewrite也不会发生转换。

location ~ ^/local {
	proxy_pass http://apiserver/foreign; // /local会直接传递到上游服务器,而不会转成/foreign
}
location / {
	rewrite /(.*)$ /index.php?page=$1 break;
	proxy_pass http://apiserver/index; // 前面的rewrite改变了uri,此处的转换不再发生
}

此外proxy_pass在上游服务器后加不加/是不同的。

location /api {
	proxy_pass http://apiserver/new_api; # localhost/api会被转换成 apiserver/new_api
}
location /api {
	proxy_pass http://apiserver/new_api/; # 此处加了/,/api会被截断,localhost/api被转换成 apiserver/
}

add_header与proxy_ser_header

proxy_set_header是Nginx设置头部信息给到上游服务器,add_header是Nginx设置响应请求头给浏览器。

在处理跨域问题时,浏览器只会与Nginx打交道,所以add_header一般设置如下

add_header Access-Control-Allow-Origin http://localhost:8090 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,DELETE,OPTIONS";
add_header Access-Control-Allow-Credentials true;

上游服务器处理请求时,有些情况下需要读取头部信息,这里就需要proxy_set_header进行设置。

proxy_set_header Host $host; # 将原始Host头传递给上游而不是Nginx自己的hostname
proxy_set_header X-Forwarded-For $remote_addr; # 传递原始客户端的IP
proxy_set_header User-Agent $http_user_agent; # 传递原始客户端的UA信息
proxy_set_header Authorizaton ""; # 删除客户端请求的敏感头信息
proxy_set_header X-Proxy-ID $server_name; # 设置自定义头部

需要注意的是,proxy_set_header 会覆盖之前设置的同名头。可以通过添加标志来附加头值而不是替换。在进行反向代理配置时,合理设置请求头可以帮助后端服务器获得原始的客户端信息,提高安全性和日志分析效果。

例子

下面通过一个例子具体看下Nginx如何配置来实现跨域的问题。原有系统部署在ip_a:31023,内部直接请求了ip_b:8000 和 ip_c:31298。因为域名和端口号都不相同,自然会有跨域问题。

现在第一步是启动一个Nginx,作为原服务的配置文件部署在ip_a下,所以系统访问Nginx不存在跨域的问题。第二步修改请求接口的方式,从直接请求改为请求代理服务器。第三步是修改Nginx配置,通过不同的uri将请求代理到不同的上游服务器中。

在Nginx的配置里,server要监听ip_a的端口号,同时利用location、add_header、proxy_set_header、proxy_pass等配置项共同完成。

server {
    listen 31023;
    server_name localhost;
    
    location /emop {
        add_header Access-Control-Allow-Origin http://ip_a:31023 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,DELETE,OPTIONS";
        add_header Access-Control-Allow-Credentials true;
        proxy_pass http://ip_b:8000;
    }
    
    location /omp {
        add_header Access-Control-Allow-Origin http://ip_a:31023 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,DELETE,OPTIONS";
        add_header Access-Control-Allow-Credentials true;
        proxy_pass http://ip_c:31298;
    }
}

上面就是通过Nginx反向代理解决跨域的简单配置。在实例使用过程中有几点注意事项:

  1. Nginx默认会把header中包含下划线的参数给去掉,导致获取不到该参数。解决方法是在server区块下增加underscores_in_headers on;
  2. Nginx允许使用if进行逻辑判断,但不能像其它编程语言一样进行if嵌套。例如需要cond_a和cond_b都满足的时候进行proxy_pass,下面的做法是通不过语法检查的。
location /api {
	if (cond_a) {
		if (cond_b) {
			proxy_pass http://apiserver/api;
		}
	}
}

通常这种情况下可以通过设置变量来进行绕行。满足cond_a时设置变量a,满足cond_b时设置变量b,然后设置变量c将a和b粘合起来。当c的设置达到cond_a&&cond_b时执行相应的操作。

location /api {
	set $a 0;
	if (cond_a) set $a 1;
	set $b 0;
	if (cond_b) set $b 1;
	set $c "${a}${b}";
	if ($c = 11) {
		proxy_pass http://apiserver/api;
	}
}

本地开发时使用Nginx代理

上面介绍了在实际生产环境如何通过配置反向代理实现跨域,那么在本地开发时如何解决跨域的问题呢?一种方法是通过禁用浏览器的web-security在浏览器层绕过跨域,另一种方法就是同样利用Nginx,整体的配置与上面是类似的。
假设本地服务启动之后运行在localhost:8080上。通过修改Nginx配置文件指定Nginx启动后监听的端口号,假设是8090。

第一步将代码中/mop和/omp的请求交给Nginx处理。

第二步Nginx配置中监听/中路径,将请求转给本地服务,这样才能正常打开页面。

server {
    listen 8090;
    server_name localhost;
    ...
    location / {
        proxy_pass http://localhost:8080;
    }
}

第三步与前面类似,将不同url请求转发到相应的上游服务器中,不再赘述。

总结

本文介绍了Nginx中的一些指令的配置,通过配置不同的头部和proxy_pass可以达到解决跨域的效果。在实例使用中,还有一些文章中没有介经到的点,例如日志的配置、upstream、反向代理服务器的性能调优等。同样利用Nginx还可以实现流量染色、灰度等场景,大家有兴趣的话也可以研究一下。

posted @ 2023-09-01 16:48  大脸菌  阅读(337)  评论(0编辑  收藏  举报