利用nginx实现302重定向自动跟随


实验场景

  • 前端域名
http://abc.nestealin.com/
  • 配置如下

    server {
        listen    80 ;
        listen    443 ssl;
        listen    8443 ssl;
        server_name  abc.nestealin.com;
        charset utf-8;
        access_log  logs/abc.nestealin.com.access.main.log  main;
        error_log  logs/abc.nestealin.com.error.log error;
    
        ssl_certificate      /usr/local/nginx/conf/cert/server.cer;
        ssl_certificate_key  /usr/local/nginx/conf/cert/server.key;
        ssl_session_timeout  5m;
        ssl_prefer_server_ciphers on;
      
        location  / {
            # include firewall.conf;
            proxy_redirect off;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Real-Port $remote_port;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://192.168.1.23:12345;
        }
    }
  • 后端服务
http://192.168.1.23:12345/login
  • 配置如下

    server {
        listen 12345;
        charset utf-8;
        access_log logs/$http_host.access.main.log main;
        error_log logs/test.error.crit.log crit;
    
        location /login {
            add_header Content-Type 'application/json';
            return 200 "uri: $uri";
        }
    
        location / {
            return 302 http://abc.nestealin.com/login;
        }
    }

实验背景

  • 场景1:
    • 当正常请求域名根路径,后端服务会有一次让客户端302到 /login 的动作

正常请求,会给客户端返回302,然后再去请求 /login 路径.

  • 浏览器请求如下
  • curl 下查看跟随跳转
nestealin >> ~ # curl http://abc.nestealin.com -vL
*   Trying 192.168.1.11...
* TCP_NODELAY set
* Connected to abc.nestealin.com (192.168.1.11) port 80 (#0)
> GET / HTTP/1.1
> Host: abc.nestealin.com
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 302 Moved Temporarily
< Server: nginx
< Date: Wed, 04 Aug 2021 15:27:36 GMT
< Content-Type: text/html
< Content-Length: 138
< Connection: keep-alive
< Keep-Alive: timeout=120
< Location: http://abc.nestealin.com/login
<
* Ignoring the response-body
* Connection #0 to host abc.nestealin.com left intact
* Issue another request to this URL: 'http://abc.nestealin.com/login'
* Found bundle for host abc.nestealin.com: 0x7fcb7dd15ee0 [can pipeline]
* Could pipeline, but not asked to!
* Re-using existing connection! (#0) with host abc.nestealin.com
* Connected to abc.nestealin.com (192.168.1.11) port 80 (#0)
> GET /login HTTP/1.1
> Host: abc.nestealin.com
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Wed, 04 Aug 2021 15:27:36 GMT
< Content-Type: application/json
< Content-Length: 11
< Connection: keep-alive
< Keep-Alive: timeout=120
<
* Connection #0 to host abc.nestealin.com left intact
uri: /login* Closing connection 0
  • 由上可以看出:

    • 客户端先请求了 http://abc.nestealin.com/ 这个URL
    • 然后服务端响应了 302 状态码,并指定 Location: http://abc.nestealin.com/login 这个 URL 让客户端进行重定向请求
    • 最终客户端通过请求 http://abc.nestealin.com/login 获取数据,得到状态码200

    由此发现,当发升重定向时,存在客户端同一个域名需要请求两次的情况,”体验感受”没那么友好。

  • 场景2:
    • 在外网通过其他端口 (例如: 8443) 请求后端服务的根路径后,会重定向到默认端口的 /login 路径.
root@test >> ~ # curl https://abc.nestealin.com:8443 -IL
HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Wed, 04 Aug 2021 12:18:02 GMT
Content-Type: text/html
Content-Length: 138
Connection: keep-alive
Keep-Alive: timeout=120
Location: http://abc.nestealin.com/login

curl: (7) couldn't connect to host
  • 但因为外网80端口禁用,所以访问失败。

解决方式

  • 利用 error_page 指令实现重定向跟随

    • 核心配置如下

      location @handle_redirect {
          resolver 223.5.5.5;
          set $saved_redirect_location '$upstream_http_location';
          proxy_pass $saved_redirect_location;
      }
      
      location  / {
          proxy_pass http://192.168.1.23:12345;
          # 对 upstream 状态码检查,实现 error_page 错误重定向
          proxy_intercept_errors on;
          # error_page 指令默认只检查了第一次后端返回的状态码,开启后可以跟随多次重定向。
          recursive_error_pages on;
          # 根据状态码执行对应操作,以下为301、302、307状态码都会触发
          error_page 301 302 307 = @handle_redirect;
      }
    • 完整配置参考

      server {
          listen    80 ;
          listen    443 ssl;
          listen    8443 ssl;
          server_name  abc.nestealin.com;
          charset utf-8;
          access_log  logs/abc.nestealin.com.access.main.log  main;
          error_log  logs/abc.nestealin.com.error.log error;
      
          ssl_certificate      /usr/local/nginx/conf/cert/server.cer;
          ssl_certificate_key  /usr/local/nginx/conf/cert/server.key;
          ssl_session_timeout  5m;
          ssl_prefer_server_ciphers on;
      
      	location @handle_redirect {
              resolver 223.5.5.5;
              set $saved_redirect_location '$upstream_http_location';
              # include firewall.conf;
              proxy_redirect off;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header X-Real-Port $remote_port;
              proxy_set_header X-Forwarded-Proto $scheme;
              proxy_pass $saved_redirect_location;
          }
      
          location  / {
              # include firewall.conf;
              proxy_redirect off;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header X-Real-Port $remote_port;
              proxy_set_header X-Forwarded-Proto $scheme;
              proxy_pass http://192.168.7.83:12345;
              proxy_intercept_errors on;
              recursive_error_pages on;
              error_page 301 302 307 = @handle_redirect;
          }
      }
      
      • 因为 error_page 捕获后是由 nginx 发起的重定向访问,所以若需透传客户端相关信息,那么也要在 handle_redirect 内部方法下增加响应透传头部,否则重定向后只会拿到 nginx 本机信息。

实验效果

  • 场景1:

    • 只存在一次请求,直接获取200状态码

    • curl 侧观察,直接获取200状态码

      nestealin >> ~ # curl https://abc.nestealin.com:8443/ -Lv
      *   Trying 192.168.1.11...
      * TCP_NODELAY set
      * Connected to abc.nestealin.com (192.168.1.11) port 8443 (#0)
      * ALPN, offering h2
      * ALPN, offering http/1.1
      * successfully set certificate verify locations:
      *   CAfile: /etc/ssl/cert.pem
        CApath: none
      * TLSv1.2 (OUT), TLS handshake, Client hello (1):
      * TLSv1.2 (IN), TLS handshake, Server hello (2):
      * TLSv1.2 (IN), TLS handshake, Certificate (11):
      * TLSv1.2 (IN), TLS handshake, Server key exchange (12):
      * TLSv1.2 (IN), TLS handshake, Server finished (14):
      * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
      * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
      * TLSv1.2 (OUT), TLS handshake, Finished (20):
      * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
      * TLSv1.2 (IN), TLS handshake, Finished (20):
      * SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
      * ALPN, server accepted to use h2
      * Server certificate:
      *  subject: CN=*.nestealin.com
      *  start date: Jun 19 11:18:17 2021 GMT
      *  expire date: Sep 17 11:18:16 2021 GMT
      *  subjectAltName: host "abc.nestealin.com" matched cert's "*.nestealin.com"
      *  issuer: C=US; O=Let's Encrypt; CN=R3
      *  SSL certificate verify ok.
      * Using HTTP2, server supports multi-use
      * Connection state changed (HTTP/2 confirmed)
      * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
      * Using Stream ID: 1 (easy handle 0x7fe05200e800)
      > GET / HTTP/2
      > Host: abc.nestealin.com:8443
      > User-Agent: curl/7.64.1
      > Accept: */*
      >
      * Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
      < HTTP/2 200
      < server: nginx
      < date: Wed, 04 Aug 2021 12:15:28 GMT
      < content-type: application/json
      < content-length: 11
      <
      * Connection #0 to host abc.nestealin.com left intact
      uri: /login* Closing connection 0
  • 场景2:

    • 带端口访问后,同样在内部完成重定向,直接返回200给客户端

      root@test >> ~ # curl https://abc.nestealin.com:8443 -vL
      * About to connect() to abc.nestealin.com port 8443 (#0)
      *   Trying 219.11.74.12... connected
      * Connected to abc.nestealin.com (219.11.74.12) port 8443 (#0)
      * Initializing NSS with certpath: sql:/etc/pki/nssdb
      *   CAfile: /etc/pki/tls/certs/ca-bundle.crt
        CApath: none
      * Server certificate:
      * 	subject: CN=*.nestealin.com
      * 	start date: Jun 19 11:18:17 2021 GMT
      * 	expire date: Sep 17 11:18:16 2021 GMT
      * 	common name: *.nestealin.com
      * 	issuer: CN=R3,O=Let's Encrypt,C=US
      > GET / HTTP/1.1
      > User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.44 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
      > Host: abc.nestealin.com:8443
      > Accept: */*
      >
      < HTTP/1.1 200 OK
      < Server: nginx
      < Date: Wed, 04 Aug 2021 12:08:28 GMT
      < Content-Type: application/json
      < Content-Length: 11
      < Connection: keep-alive
      < Keep-Alive: timeout=120
      <
      * Connection #0 to host abc.nestealin.com left intact
      * Closing connection #0
      uri: /login

特别注意

  • 在做内部重定向时,nginx需要对重定向域名做一次域名解析,一定务必保证ngxin可以解析该域名,否则会因无法解析直接返回502.

    • curl 请求直接502
    root@test >> ~ # curl https://abc.nestealin.com:8443 -vL
    * About to connect() to abc.nestealin.com port 8443 (#0)
    *   Trying 219.11.74.18... connected
    * Connected to abc.nestealin.com (219.11.74.12) port 8443 (#0)
    * Initializing NSS with certpath: sql:/etc/pki/nssdb
    *   CAfile: /etc/pki/tls/certs/ca-bundle.crt
      CApath: none
    * Server certificate:
    * 	subject: CN=*.nestealin.com
    * 	start date: Jun 19 11:18:17 2021 GMT
    * 	expire date: Sep 17 11:18:16 2021 GMT
    * 	common name: *.nestealin.com
    * 	issuer: CN=R3,O=Let's Encrypt,C=US
    > GET / HTTP/1.1
    > User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.44 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
    > Host: abc.nestealin.com:8443
    > Accept: */*
    >
    < HTTP/1.1 502 Bad Gateway
    < Server: nginx
    < Date: Wed, 04 Aug 2021 12:08:13 GMT
    < Content-Type: text/html; charset=utf-8
    < Content-Length: 150
    < Connection: keep-alive
    < Keep-Alive: timeout=120
    <
    <html>
    <head><title>502 Bad Gateway</title></head>
    <body>
    <center><h1>502 Bad Gateway</h1></center>
    <hr><center>nginx</center>
    </body>
    </html>
    * Connection #0 to host abc.nestealin.com left intact
    * Closing connection #0
    • nginx错误日志提示如下:

      2021/08/04 22:13:29 [error] 1490537#0: *4009172 no resolver defined to resolve abc.nestealin.com while sending to client, client: 192.168.1.3, server: test.neshomelab.com, request: "GET / HTTP/2.0", host: "abc.nestealin.com:8443"


文章作者: NesTeaLin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 NesTeaLin !
  目录