Skip to content

nginx实现302重定向自动跟随

实验场景

  • 前端域名
none
http://abc.xxx.com/
http://abc.xxx.com/
  • 配置如下
server {
    listen    80 ;
    listen    443 ssl;
    listen    8443 ssl;
    server_name  abc.xxx.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;
    }
}
server {
    listen    80 ;
    listen    443 ssl;
    listen    8443 ssl;
    server_name  abc.xxx.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
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;
    }
}
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 路径

image-20220323150842963

  • curl 下查看跟随跳转
nestealin >> ~ # curl http://abc.xxx.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.xxx.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.xxx.com/login
<
* Ignoring the response-body
* Connection #0 to host abc.xxx.com left intact
* Issue another request to this URL: 'http://abc.xxx.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.xxx.com
* Connected to abc.nestealin.com (192.168.1.11) port 80 (#0)
> GET /login HTTP/1.1
> Host: abc.xxx.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.xxx.com left intact
uri: /login* Closing connection 0
nestealin >> ~ # curl http://abc.xxx.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.xxx.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.xxx.com/login
<
* Ignoring the response-body
* Connection #0 to host abc.xxx.com left intact
* Issue another request to this URL: 'http://abc.xxx.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.xxx.com
* Connected to abc.nestealin.com (192.168.1.11) port 80 (#0)
> GET /login HTTP/1.1
> Host: abc.xxx.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.xxx.com left intact
uri: /login* Closing connection 0
  • 由上可以看出:

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

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

  • 场景2:

    • 在外网通过其他端口 (例如: 8443) 请求后端服务的根路径后,会重定向到默认端口的 /login 路径
root@test >> ~ # curl https://abc.xxx.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.xxx.com/login

curl: (7) couldn't connect to host
但因为外网80端口禁用,所以访问失败
root@test >> ~ # curl https://abc.xxx.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.xxx.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;
}
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.xxx.com;
    charset utf-8;
    access_log  logs/abc.xxx.com.access.main.log  main;
    error_log  logs/abc.xxx.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';
        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  / {
        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;
    }
}
server {
    listen    80 ;
    listen    443 ssl;
    listen    8443 ssl;
    server_name  abc.xxx.com;
    charset utf-8;
    access_log  logs/abc.xxx.com.access.main.log  main;
    error_log  logs/abc.xxx.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';
        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  / {
        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 本机信息

特别注意

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