为何表单参数无法通过Apache Handler传递至后端端点?
在/etc/httpd/conf.d/perl.conf中配置了PerlAccessHandler MyApache::TokenCheck,对应的TokenCheck.pm处理代码如下:
sub handler { my $r = shift; my $cookie = CGI::Cookie->fetch($r); my %cookies = map { split /=/, $_, 2 } split /; /, $cookie->{'sessionID'}; my $sessionID = $cookies{'sessionID'}; if( $r->method eq 'POST' ) { my $cgi = CGI->new($r); my $token = $cgi->param('csfr_token'); my $ac = MyPkg::CheckSess->new; if( !$ac->validSessionWithToken($sessionID, $token) ) { # redirect or something return Apache2::Const::FORBIDDEN; } return Apache2::Const::OK; } else { if ($sessionID) { my $checker = new MyPkg::CheckSess(); if ($checker->validSessionID($sessionID)) { return Apache2::Const::OK; } else { return Apache2::Const::FORBIDDEN; } } else { return Apache2::Const::FORBIDDEN; } } # end else }
遇到的问题:通过$r(Apache2::RequestRec)实例化CGI对象时能正常获取表单数据,但这些表单变量无法传递到后端端点;尝试使用Apache2::RequestRec的read_post方法也得到同样结果。移除该处理程序或不在处理程序中读取CGI参数时,表单可正常提交到后端。
原因
Apache的HTTP请求体是一次性流数据,当你在PerlAccessHandler阶段用CGI->new($r)或者read_post读取POST内容时,请求体的输入指针会移动到流的末尾。后续后端处理程序(比如CGI脚本、其他业务模块)再尝试读取请求体时,就会发现没有数据可读,导致表单变量无法传递。
解决方案
使用
Apache2::Request模块读取参数
改用Apache2::Request来获取POST参数,它会将读取到的请求体数据缓存起来,供后续处理流程复用,不会直接消耗请求体流。示例代码修改如下:use Apache2::Request; sub handler { my $r = shift; # ... 其他cookie处理代码 ... if( $r->method eq 'POST' ) { my $req = Apache2::Request->new($r); my $token = $req->param('csfr_token'); # ... 后续验证逻辑 ... } # ... 其他代码 ... }避免在早期阶段读取完整请求体
如果不需要提前读取所有POST参数,可考虑将CSRF token放在HTTP头(比如自定义X-CSRF-Token头)中,这样在PerlAccessHandler阶段只需读取请求头,不会触碰请求体,也就不会影响后端对表单数据的读取。手动重置请求体指针(不推荐)
若必须使用原生方式读取请求体,可在读取后将内容重新写回请求的输入缓冲区,并重置指针。这种方法需要处理更多底层细节,容易出错,示例思路如下:if( $r->method eq 'POST' ) { my $content = $r->read_post(); # 从$content中解析出csfr_token # ... 验证逻辑 ... # 将内容重新写回输入流 $r->push_handlers(PerlFixupHandler => sub { my $r = shift; $r->headers_in->set('Content-Length', length($content)); open my $fh, '<', \$content; $r->input_fh($fh); return Apache2::Const::OK; }); }
内容的提问来源于stack exchange,提问作者jmcneirney

