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到
正常请求,会给客户端返回302,然后再去请求 /login
路径
- 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
路径
- 在外网通过其他端口 (例如: 8443) 请求后端服务的根路径后,会重定向到默认端口的
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