HTML 表单不支持 PUT
、PATCH
或 DELETE
行为。但是我们在使用Laravel框架的时候,仍然可以定义一个仅支持PUT的路由:
Route::put(‘update‘, function (\Illuminate\Http\Request $request) {
dd($_SERVER[‘REQUEST_METHOD‘], $request->getMethod());
});
只需要在html的表单中加入:
<form action="/update" method="POST">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>
使用起来是挺简单的,但你是否注意到,Laravel的表单方法伪造只有在form表单的method="POST"才会生效,这是为什么?带着这个小问题,我们来看看框架是怎么实现伪造的。
阅读源码
Laravel的路由匹配http请求时,是调用Request
类getMethod()
方法获取method
属性值,再根据这个属性值进行匹配然后分发路由的。
我们追踪\Illuminate\Http\Request类的getMethod方法,发现是继承自\Symfony\Component\HttpFoundation,源码如下:
public function getMethod()
{
// 追踪了method这个属性,发现除了在这个方法内,其他地方都是把这个属性设置为null,所以第一次调用getMethod方法时,$this->method必定为null。
if (null !== $this->method) {
return $this->method;
}
// $_SERVER[‘REQUEST_METHOD‘]为HTTP的请求方式
$this->method = strtoupper($this->server->get(‘REQUEST_METHOD‘, ‘GET‘));
// 只有POST请求,才可以进行表单方法伪造。
if (‘POST‘ !== $this->method) {
return $this->method;
}
$method = $this->headers->get(‘X-HTTP-METHOD-OVERRIDE‘);
// 关于self::$httpMethodParameterOverride这个属性,Laravel框架在启动之前,便把它设置会true了,所以进入这个条件分支
if (!$method && self::$httpMethodParameterOverride) {
// 这一句代码的意思优先拿$_POST中的_method字段,若不存在则去拿$_GET中的_method字段。
$method = $this->request->get(‘_method‘, $this->query->get(‘_method‘, ‘POST‘));
}
// 伪造的请求方式必须是字符串
if (!\is_string($method)) {
return $this->method;
}
$method = strtoupper($method);
// 必须是合法的请求方式
if (\in_array($method, [‘GET‘, ‘HEAD‘, ‘POST‘, ‘PUT‘, ‘DELETE‘, ‘CONNECT‘, ‘OPTIONS‘, ‘PATCH‘, ‘PURGE‘, ‘TRACE‘], true)) {
return $this->method = $method;
}
if (!preg_match(‘/^[A-Z]++$/D‘, $method)) {
throw new SuspiciousOperationException(sprintf(‘Invalid method override "%s".‘, $method));
}
return $this->method = $method;
}
阅读这段代码,我们就可以知道,Laravel的表单方法伪造只不过是修改 \Illuminate\Http\Request
类的method
属性值,并不会改变http的真实请求方式。
到这里,关于Laravel的表单方法伪造只有在form表单的method="POST"才会生效
这个问题,我们可以轻易的解释了:这一切只不过是框架与HTML表单之间的一个约定,定义了表单方法伪造的方式。