laravel CSRF 保护
在开始之前让我们来实现上述表单访问伪造的完整示例,为简单起见,我们在路由闭包中实现所有业务代码:
Route::get('task/{id}/delete', function ($id) {
return '<form method="post" action="' . route('task.delete', [$id]) . '">
<input type="hidden" name="_method" value="DELETE">
<button type="submit">删除任务</button>
</form>';
});
Route::delete('task/{id}', function ($id) {
return 'Delete Task ' . $id;
})->name('task.delete');
在 http://blog.test/task/1/delete
点击「删除任务」按钮提交表单,会显示 419 异常页面:
初学者可能会困惑,这是什么原因呢?
不得不说,Laravel 5.7 引入的错误提示页面虽然好看,但是错误提示信息太少,这其实是因为默认情况下,为了安全考虑,Laravel 期望所有路由都是「只读」操作的(对应请求方式是 GET、HEAD、OPTIONS),如果路由执行的是「写入」操作(对应请求方式是 POST、PUT、PATCH、DELETE),则需要传入一个隐藏的 Token 字段(_token
)以避免[跨站请求伪造攻击](CSRF)。在我们上面的示例中,请求方式是 DELETE,但是并没有传递 _token
字段,所以会出现异常。
注:跨站请求伪造是一种通过伪装授权用户的请求来攻击授信网站的恶意漏洞,关于跨站请求伪造攻击可以参考维基百科了解明细:https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0。
避免跨站请求伪造攻击的措施就是对写入操作采用非 GET 方式请求,同时在请求数据中添加校验 Token 字段,Laravel 也是这么做的,这个 Token 值会在渲染表单页面时通过 Session 生成,然后传入页面,在每次提交表单时带上这个 Token 值即可实现安全写入,因为第三方站点是不可能拿到这个 Token 值的,所以由第三方站点提交的请求会被拒绝,从而避免 CSRF 攻击。
在 Laravel 中,和表单方法伪造一样,支持通过 HTML 表单隐藏字段传递这个值:
Route::get('task/{id}/delete', function ($id) {
return '<form method="post" action="' . route('task.delete', [$id]) . '">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="' . csrf_token() . '">
<button type="submit">删除任务</button>
</form>';
});
这样我们再次访问 http://blog.test/task/1/delete
页面点击「删除任务」按钮,即可成功提交表单。
当然,如果你是在 JavaScript 脚本中执行 HTTP 请求,也可以很方便的传递这个 Token 值执行写入操作,首先需要在 HTML <head>
标签内新增一个 <meta>
元素来存储 Token 值:
<meta name="csrf-token" content="<?php echo csrf_token(); ?>" id="csrf-token">
然后我们在 JavaScript 脚本中将这个 Token 值放到一个全局请求头设置中,以便每个 HTTP 请求都会带上这个头信息,避免每次发起请求都要添加这个字段。如果你使用的是 jQuery 的话,可以这么做:
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
如果你使用的是 Vue 的话,可以这么做:
Vue.http.interceptors.push((request, next) => {
request.headers['X-CSRF-TOKEN'] = document.querySelector('#csrf-token').getAttribute('content');
next();
});
Laravel 会在每次请求都检查请求头中是否包含 X-CSRF-TOKEN
,并检查其值是否和 Session 中的 Token 值是否一致。
注:如果你使用了 Laravel 自带的
assets/js/bootstrap.js
, 则上述 Vue 请求头设置不需要自己编写,因为bootstrap.js
中已经包含了这个逻辑。