声明:本文是以Nginx和PHP的交互过程来说明具体问题。
以下分两个部分来探讨
客户端参数是如何传递到服务器应用程序中的
一个完整简易的Http请求Request/Response过程:
服务器接收到客户端Http请求,选择CGI script来处理请求, 通过meta-variable来传递请求数据, 将客户端请求转为一个CGI请求,执行script, 把CGI响应转为客户端响应。
说明:下文如没有特殊说明
0、Nginx将指代上文的服务器。
1、php-cgi将指代上文的CGI script。【可以理解成CGI Server, 而Nginx中fastcgi module可以理解成CGI Client,所以有CGI请求和响应】
2、meta-variable典型的变量名Name有(参考CGI翻译:CGI1.1):
meta-variable-name = "AUTH_TYPE" | "CONTENT_LENGTH" | "CONTENT_TYPE" | "GATEWAY_INTERFACE" | "PATH_INFO" | "PATH_TRANSLATED" |"QUERY_STRING" | "REMOTE_ADDR" |"REMOTE_HOST" | "REMOTE_IDENT" |"REMOTE_USER" | "REQUEST_METHOD" |"SCRIPT_NAME" | "SERVER_NAME" | "SERVER_PORT" | "SERVER_PROTOCOL" |"SERVER_SOFTWARE" | scheme |
介绍了客户端请求的大致过程,下面我们来看看客户端参数传递到Nginx,在由Nginx传递到PHP的过程,分两种情况:一种是通过Http header(协议头)传递的,另一种不是通过协议头传递。
一、客户端不是通过协议头传递的参数
Nginx接收到客户端Http请求,(《HTTP协议》这本书上提到老的浏览器回在首部传递Client_IP参数,暂不清楚现在的浏览器是如何传递的,Nginx是如何获取到客户端IP的)经处理后把meta-variable中常用的变量需要的值保存在Nginx变量中(比如客户端IP保存在$remote_addr变量中), 通过fastcgi模块中的fastcgi_param指令(Nginx fastcgi手册)传递给php-cgi, php-cgi接收到存到全局变量$_SERVER中。
在nginx/config/fastcgi_param中可以参考传递的参数
fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param REQUEST_SCHEME $scheme; fastcgi_param HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; # PHP only, required if PHP was built with --enable-force-cgi-redirect fastcgi_param REDIRECT_STATUS 200;
以上参数通过PHP $_SERVER可以获取对应的变量内容
$client_ip = $_SERVER['REMOTE_ADDR']; // 如果nginx中不指代fastcgi_param REMOTE_ADDR $remote_addr;将获取不到$_SERVER['REMOTE_ADDR']内容
二、客户端通过协议头传递的参数
这部分参数有约定的参数(Referer、UA)和自定义参数比如AA、BB等
curl ‘niliu.me’ -H ‘x-forwarded-for:1.1.1.1’ -H ‘AA:aaaa’ -H ‘bb:bbbb’
默认支持中横线分隔,Nginx接收到后添加HTTP_前缀,存到Nginx变量中:例如:$http_x_forwarded_for;【全部小写】。
但是没有通过fastcgi_param传递,php $_SERVER中也可以获取到,
var_dump($_SERVER); // 显示部分结果 ["HTTP_BB"]=> string(4) "bbbb" ["HTTP_AA"]=> string(4) "aaaa" ["HTTP_X_FORWARDED_FOR"]=> string(7) "1.1.1.1"
这部分传递过程是CGI范畴定义的规则了,暂时没搞清楚,问题在CSDN上有提——《Http请求header中自定义的参数是如何传到PHP SERVER变量中的》,欢迎大家来论坛回帖交流。
多说一句,通过协议头传的参数名默认不支持下滑线,可以通过如下方式开启:
开启下滑线nginx conf中设置[具体使用参考Nginx官网手册]
underscores_in_headers on;【默认是关闭的,但是Nginxv1.14.0默认是开启的】
curl ‘niliu.me’ -H ‘x_forwarded_for:1.1.1.1’
Syntax: underscores_in_headers on | off; Default: underscores_in_headers off; Context: http, server Enables or disables the use of underscores in client request header fields. When the use of underscores is disabled, request header fields whose names contain underscores are marked as invalid and become subject to the ignore_invalid_headers directive.
应用程序如何获取客户端真实IP
讨论了参数的传递过程,下面来看看如何获取客户端真实IP,分两种情况:
0、没有代理服务器
Client -> 源Nginx Server
// $_SERVER['REMOTE_ADDR']中存的就是客户端真实IP $client_ip = $_SERVER['REMOTE_ADDR'];
1、有代理服务器
Client -> Proxy Nginx Server -> 源Nginx Server
一般在Proxy Nginx Server中添加如下指令
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
PHP的$_SERVER[‘HTTP_X_FORWARDED_FOR’]取真实client ip
综上,实现一个通用获取客户端真实IP的方法,
伪算法
// 0、定义变量 $forwarded = $_SERVER['HTTP_X_FORWARDED_FOR']; $ip = $_SERVER['REMOTE_ADDR']; // 1、如果$forwarded为空, 真实IP就是$ip $real_ip = $ip; // 2、如果$forwarded不为空,取$forwarded中逗号分隔最后一个值$source_ip if ($source_ip) { // 检查$ip是否是内网IP if ($ip是内网IP) { $real_ip = $source_ip; } else { $real_ip = $ip; } }
具体算法实现见下篇文章《PHP实现获取客户端真实IP》,文章涉及到一些核心技术点,特需密码访问,如有需要,可留言。
参考:
nginx配置:支持phpfastcgi,nginx和php-cgi通信,部分nginx常量解释
使用nginx后如何在web应用中获取用户ip及原理解释
NGINX多层转发或使用CDN之后如何获取用户真实IP
你确定你真的懂Nginx与PHP的交互
http://php.net/manual/zh/reserved.variables.server.php
通过修改http请求的header请求头来伪造ip