[ Laravel 5.5 文档 ] 处理用户请求 —— 使用中间件 VerifyCsrfToken 避免 CSRF 攻击

简介&功能演示

跨站请求伪造(CSRF)是一种通过伪装授权用户的请求来攻击授信网站的恶意漏洞。

Laravel 通过自带的 CSRF 保护中间件让避免应用遭到跨站请求伪造攻击变得简单:Laravel 会自动为每一个被应用管理的有效用户会话生成一个 CSRF “令牌”,然后将该令牌存放在 Session 中,该令牌用于验证授权用户和发起请求者是否是同一个人。

任何时候在 Laravel 应用中定义 HTML 表单,都需要在表单中引入 CSRF 令牌字段,这样 CSRF 保护中间件才能够对请求进行验证。要想生成包含 CSRF 令牌的隐藏输入字段,可以使用辅助函数 csrf_field

<form method="POST" action="/profile">
    {{ csrf_field() }}
    ...
</form>

中间件组 web 中的中间件 VerifyCsrfToken 会自动为我们验证请求输入的 token 值和 Session 中存储的 token 是否一致,如果没有传递该字段或者传递过来的字段值和 Session 中存储的数值不一致,则会抛出异常。

为了演示该功能,我们在 routes/web.php 中定义一组测试路由:

Route::get('form_without_csrf_token', function (){
    return '<form method="POST" action="/hello_from_form"><button type="submit">提交</button></form>';
});

Route::get('form_with_csrf_token', function () {
    return '<form method="POST" action="/hello_from_form">' . csrf_field() . '<button type="submit">提交</button></form>';
});

Route::post('hello_from_form', function (){
   return 'hello laravel!';
});

我们在浏览器中访问 http://blog.dev/form_without_csrf_token 并点击页面上的提交按钮时,页面报错,抛出 MethodNotAllowedHttpException 异常,出现这个异常往往就是意味着没有传递 CSRF 令牌字段或者传递的令牌字段不正确:

而当我们访问 http://blog.dev/form_csrf_token 并点击页面上的提交按钮时,页面显示正常。

注:CSRF 中间件只只作用于 routes/web.php 中定义的路由,因为该文件下的路由分配了 web 中间件组,而 VerifyCsrfToken 位于 web 中间件组中。

排除指定 URL 不做 CSRF 安全校验

有时候我们需要从 CSRF 保护中间件中排除一些 URL,例如,如果你使用了第三方支付系统(如支付宝或微信支付)来处理支付并用到他们提供的回调功能,这时候就需要从 Laravel 的 CSRF 保护中间件中排除回调处理器路由,因为第三方支付系统并不知道要传什么 token 值给我们定义的路由。

通常我们需要将这种类型的路由放到文件 routes/web.php 之外,比如 routes/api.php。不过,如果必须要加到 routes/web.php 中的话,你也可以在 VerifyCsrfToken 中间件中将要排除的 URL 添加到 $except 属性数组:

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * 从 CSRF 验证中排除的 URL
     *
     * @var array
     */
    protected $except = [
        'alipay/*',
    ];
}

X-CSRF-Token

除了将 CSRF 令牌作为 POST 参数进行验证外,还可以通过设置 X-CSRF-Token 请求头来实现验证,VerifyCsrfToken 中间件会检查 X-CSRF-TOKEN 请求头。实现方式如下,首先创建一个 meta 标签并将令牌保存到该 meta 标签:

<meta name="csrf-token" content="{{ csrf_token() }}">

然后在 js 库(如 jQuery)中添加该令牌到所有请求头,这为基于 AJAX 的请求提供了简单、方便的方式来避免 CSRF 攻击:

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

CSRF 令牌和 JavaScript

构建 JavaScript 驱动的应用时,给 JavaScript HTTP 库每个出口请求添加 CSRF 令牌字段很方便。默认情况下,resources/assets/js/bootstrap.js 文件已经通过 Axios HTTP 库注册了 csrf-token 这个 meta 标签的值作为令牌字段值,如果你没有使用这个库,则需要手动配置来实现类似的功能。

X-XSRF-Token

Laravel 还会将 CSRF 令牌保存到名为 XSRF-TOKEN 的 Cookie 中,你可以使用该 Cookie 值来设置 X-XSRF-TOKEN 请求头。

一些 JavaScript 框架,比如 Angular 和 Axios,会为你自动进行设置,基本上你不太需要手动设置这个值。

最后,VerifyCsrfToken 中间件框架底层实现源码位于 vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php,感兴趣的同学可以去一窥究竟。

学院君 has written 703 articles

资深PHP工程师,Laravel学院院长

7 thoughts on “[ Laravel 5.5 文档 ] 处理用户请求 —— 使用中间件 VerifyCsrfToken 避免 CSRF 攻击

  1. zhangjun says:

    提一个关于布局的意见,在文档的详情页,能不能把内容目录这部分做成固定在屏幕左侧,而不是相关文章。
    看很长的文档的时候,总是要把页面滚动上去找目录,很麻烦,而且相关文章里面推荐的文章,都是些相关度不高的文章
    比如我在看laravel5.4 eloquent orm的部分,相关的文章里面都是些5.0,5.1的eloquent orm,事实上我想很多人应该都不关心这些吧。

发表评论

标记为*的字段是必填项(邮箱地址不会被公开)

你可以使用这些HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>