使用 Nginx 转发请求。把跨域的接口写成调本域的接口,然后将这些接口转发到真正的请求地址。

原文: 别整复杂了!Nginx 可以轻松搞定跨域问题

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

  • Access-Control-Allow-Origin 用于设置允许跨域请求源地址 (预检请求和正式请求在跨域时候都会验证)
  • Access-Control-Allow-Headers 跨域允许携带的特殊头信息字段 (只在预检请求验证)
  • Access-Control-Allow-Methods 跨域允许的请求方法或者说HTTP动词 (只在预检请求验证)
  • Access-Control-Allow-Credentials 是否允许跨域使用cookies,如果要跨域使用cookies,可以添加上此请求响应头,值设为true(设置或者不设置,都不会影响请求发送,只会影响在跨域时候是否要携带cookies,但是如果设置,预检请求和正式请求都需要设置)。不过不建议跨域使用(项目中用到过,不过不稳定,有些浏览器带不过去),除非必要,因为有很多方案可以代替。

准备: 前端网站地址:http://localhost:8080 服务端网址:http://localhost:59200

nginx配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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;   
    }  
} 

配置实践

  1. 先启动一个http服务

golang 实现的一个简单的http服务示例,使用http.ServeMux控制路由的HTTP服务器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main
 
import (
    "fmt"
    "net/http"
)
 
func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the home page!")
}
 
func aboutHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "This is the about page.")
}
 
func main() {
    // 创建一个ServeMux对象
    mux := http.NewServeMux()
    // 注册处理函数和路径到ServeMux
    mux.HandleFunc("/", homeHandler)
    mux.HandleFunc("/about", aboutHandler)
    // 使用ServeMux启动服务器
    fmt.Println("Server is listening on port 8080...")
    if err := http.ListenAndServe(":8080", mux); err != nil {
        fmt.Printf("Error starting server: %s\n", err)
    }
}

直接运行go run main.go需要代理的http服务,监听8080端口

启动配置nginx服务

  1. 创建一个nginx.conf文件,跨域配置如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
server {
    listen       80;
    server_name  localhost;
 
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;

        if ($request_method = 'OPTIONS') {  
            add_header Access-Control-Allow-Origin 'http://localhost';  
            add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';  
            add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';  
            add_header Access-Control-Allow-Credentials 'true';  
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;  
        }  
        if ($request_method != 'OPTIONS') {  
            add_header Access-Control-Allow-Origin 'http://localhost' always;  
            add_header Access-Control-Allow-Credentials 'true';  
        }  
        proxy_pass  http://localhost:8080;   
    }
}

在docker中启动nginx

  1. 使用bitnami/nginx:1.25.5-debian-12-r1镜像启动nginx服务
1
2
3
4
5
6
docker run -d -p 80:80 --name my-nginx-cors -v ./nginx.conf:/etc/nginx/nginx.conf:ro bitnami/nginx:1.25.5-debian-12-r1
ad5be6375aa5aee5ee524c51d0586d00fe622347cb65eafa0af26e5afda59438

docker ps
CONTAINER ID   IMAGE                               COMMAND                  CREATED         STATUS         PORTS                                                   NAMES
ad5be6375aa5   bitnami/nginx:1.25.5-debian-12-r1   "/opt/bitnami/script…"   5 seconds ago   Up 4 seconds   8080/tcp, 0.0.0.0:80->80/tcp, :::80->80/tcp, 8443/tcp   my-nginx-cors
  1. 由于这里用的是bitnami/nginx镜像,配置文件位置不一样,bitnami/nginx默认配置如下

查看配置文件内容

1
docker exec -it my-nginx-cors cat /opt/bitnami/nginx/conf/nginx.conf

默认配置文件内容如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# Based on https://www.nginx.com/resources/wiki/start/topics/examples/full/#nginx-conf
# user              www www;  ## Default: nobody

worker_processes  auto;
error_log         "/opt/bitnami/nginx/logs/error.log";
pid               "/opt/bitnami/nginx/tmp/nginx.pid";

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    log_format    main '$remote_addr - $remote_user [$time_local] '
                       '"$request" $status $body_bytes_sent "$http_referer" '
                       '"$http_user_agent" "$http_x_forwarded_for"';
    access_log    "/opt/bitnami/nginx/logs/access.log" main;
    add_header    X-Frame-Options SAMEORIGIN;

    client_body_temp_path  "/opt/bitnami/nginx/tmp/client_body" 1 2;
    proxy_temp_path        "/opt/bitnami/nginx/tmp/proxy" 1 2;
    fastcgi_temp_path      "/opt/bitnami/nginx/tmp/fastcgi" 1 2;
    scgi_temp_path         "/opt/bitnami/nginx/tmp/scgi" 1 2;
    uwsgi_temp_path        "/opt/bitnami/nginx/tmp/uwsgi" 1 2;

    sendfile           on;
    tcp_nopush         on;
    tcp_nodelay        off;
    gzip               on;
    gzip_http_version  1.0;
    gzip_comp_level    2;
    gzip_proxied       any;
    gzip_types         text/plain text/css application/javascript text/xml application/xml+rss;
    keepalive_timeout  65;
    ssl_protocols      TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
    client_max_body_size 80M;
    server_tokens off;

    absolute_redirect  off;
    port_in_redirect   off;

    include  "/opt/bitnami/nginx/conf/server_blocks/*.conf";

    # HTTP Server
    server {
        # Port to listen on, can also be set in IP:PORT format
        listen  8080;

        include  "/opt/bitnami/nginx/conf/bitnami/*.conf";

        location /status {
            stub_status on;
            access_log   off;
            allow 127.0.0.1;
            deny all;
        }
    }
}

所以直接替换这个配置文件,再启动服务。

  1. 修改其中http server部分
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
    listen       80;
    server_name  localhost;
 
    location / {
        if ($request_method = 'OPTIONS') {  
            add_header Access-Control-Allow-Origin 'http://localhost';  
            add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';  
            add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';  
            add_header Access-Control-Allow-Credentials 'true';  
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;  
        }  
        if ($request_method != 'OPTIONS') {  
            add_header Access-Control-Allow-Origin 'http://localhost' always;  
            add_header Access-Control-Allow-Credentials 'true';  
        }  
        proxy_pass  http://172.20.10.3:8080;   
    }
}

注意这里的proxy_pass代理的地址要填宿主主机的ip地址,因为上面启动的http服务是直接运行在宿主机上

可以用ifconfig查看宿主机本地ip,这里查到是172.20.10.3。

  1. 修改后启动或重新加载配置
1
2
3
docker run -d -p 80:80 --name my-nginx-cors -v ./nginx.conf:/opt/bitnami/nginx/conf/nginx.conf:ro bitnami/nginx:1.25.5-debian-12-r1

docker exec -it my-nginx-cors nginx -s reload

测试跨域请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
curl -I -X OPTIONS -H "Origin: http://example.com" http://localhost
HTTP/1.1 204 No Content
Server: nginx
Date: Sun, 25 May 2025 11:46:07 GMT
Connection: keep-alive
Access-Control-Allow-Origin: http://localhost
Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization
Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
Content-Type: text/plain charset=UTF-8
Content-Length: 0

看到响应的Access-Control-Allow-Origin: http://localhost,这样表示在浏览器中存在跨域。

此时就需要修改为Access-Control-Allow-Origin: http://example.com域名来解决跨域问题。

修改之后再访问就可以看到已经发生变化。

允许多个域名跨域访问

由于Access-Control-Allow-Origin参数只允许配置单个域名或者*,当需要允许多个域名跨域访问时可以用以下方法来实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
map $http_origin $corsHost {
         default 0;
         "~http://www.example.com" http://www.example.com;
         "~http://example.com" http://example.com;
     }

     # HTTP Server
     server {
         # Port to listen on, can also be set in IP:PORT format
         listen  80;
         server_name  localhost;

         include  "/opt/bitnami/nginx/conf/bitnami/*.conf";

         location /status {
             stub_status on;
             access_log   off;
             #allow 127.0.0.1;
             #deny all;
         }

         location / {
             if ($request_method = 'OPTIONS') {
                 add_header Access-Control-Allow-Origin $corsHost;
                 add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
                 add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';
                 add_header Access-Control-Allow-Credentials 'true';
                 add_header 'Access-Control-Max-Age' 1728000;
                 add_header 'Content-Type' 'text/plain charset=UTF-8';
                 add_header 'Content-Length' 0;
                 return 204;
             }
             if ($request_method != 'OPTIONS') {
                 add_header Access-Control-Allow-Origin $corsHost always;
                 add_header Access-Control-Allow-Credentials 'true';
             }
             proxy_pass  http://172.20.10.3:8080;
         }
     }

这样就配置允许两个域名跨域访问了。

参考