美文网首页
laravel中csrf验证

laravel中csrf验证

作者: 请叫我赵大湿 | 来源:发表于2020-05-06 17:19 被阅读0次

csrf是什么

CSRF全称为跨站请求伪造(Cross-site request forgery),是一种网络攻击方式,也被称为 one-click attack 或者 session riding。

CSRF攻击利用网站对于用户网页浏览器的信任,挟持用户当前已登陆的Web应用程序,去执行并非用户本意的操作。

csrf验证演示

创建控制器:


$ php artisan make:controller CsrfController

app/Http/Controllers/CsrfController.php 创建两个方法,一个显示表单,一个提交表单:


class CsrfController extends Controller

{

    public function form()

    {

        return view('csrf.form');

    }

    public function post(Request $request)

    {

        dd($request->post());

    }

}

在 routes/web.php 中定义路由:


Route::get('form', 'CsrfController@form')->name('csrf.form');

Route::post('post', 'CsrfController@post')->name('csrf.post');

创建表单视图:resources/views/csrf/form.blade.php


<div class="container mt-5">

    <form method="post" action="{{ route('csrf.post') }}">

        <div class="form-group">

            <label for="name">用户名</label>

            <input type="text" class="form-control" name="name" id="name" placeholder="输入用户名">

        </div>

        <div class="form-group">

            <label for="pwd">密码</label>

            <input type="password" class="form-control" name="password" id="pwd" placeholder="输入密码">

        </div>

        <button type="submit" class="btn btn-primary">提交</button>

    </form>

</div>

提交表单,会报419错误:


Request URL: http://127.0.0.1:8000/post

Request Method: POST

Status Code: 419 unknown status

Remote Address: 127.0.0.1:8000

Referrer Policy: no-referrer-when-downgrade

因为表单没有携带csrf验证所需要的token,修改form表单:


第一种写法:

<form method="post" action="{{ route('csrf.post') }}">

    {{ csrf_field()}}

    <!-- ... -->

</form>

第二种写法:

<form method="post" action="{{ route('csrf.post') }}">

    <input type="hidden" value="{{csrf_token()}}" name="_token">

    <!-- ... -->

</form>

在vendor/laravel/framework/src/Illuminate/Foundation/helpers.php中:


if (! function_exists('csrf_field')) {

    /**

    * Generate a CSRF token form field.

    *

    * @return \Illuminate\Support\HtmlString

    */

    function csrf_field()

    {

        return new HtmlString('<input type="hidden" name="_token" value="'.csrf_token().'">');

    }

}

if (! function_exists('csrf_token')) {

    /**

    * Get the CSRF token value.

    *

    * @return string

    *

    * @throws \RuntimeException

    */

    function csrf_token()

    {

        $session = app('session');

        if (isset($session)) {

            return $session->token();

        }

        throw new RuntimeException('Application session store not set.');

    }

}

刷新页面,解析为以下代码:


<form method="post" action="http://127.0.0.1:8000/post">

        <input type="hidden" name="_token" value="TiOLPHQAnStZu0uffo5K8YMbuhrT4jgxbULH6x55">

        <div class="form-group">

            <label for="name">用户名</label>

            <input type="text" class="form-control" name="name" id="name" placeholder="输入用户名">

        </div>

        <div class="form-group">

            <label for="pwd">密码</label>

            <input type="password" class="form-control" name="password" id="pwd" placeholder="输入密码">

        </div>

        <button type="submit" class="btn btn-primary">提交</button>

    </form>

再次提交表单,便可打印出表单数据:


array:3 [▼

  "_token" => "TiOLPHQAnStZu0uffo5K8YMbuhrT4jgxbULH6x55"

  "name" => "123"

  "password" => "123"

]

忽略csrf验证

在app/Http/Kernel.php的$middlewareGroups将其屏蔽:


protected $middlewareGroups = [

        'web' => [

            \App\Http\Middleware\EncryptCookies::class,

            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,

            \Illuminate\Session\Middleware\StartSession::class,

            // \Illuminate\Session\Middleware\AuthenticateSession::class,

            \Illuminate\View\Middleware\ShareErrorsFromSession::class,

            //\App\Http\Middleware\VerifyCsrfToken::class,

            \Illuminate\Routing\Middleware\SubstituteBindings::class,

        ],

        'api' => [

            'throttle:60,1',

            'bindings',

        ],

    ];

在app/Http/Middleware/VerifyCsrfToken.php中间件的$except属性中添加要过滤的路由:


protected $except = [

    '/post'

];

Illuminate\Foundation\Http\Middleware\VerifyCsrfToken.php

protected function inExceptArray($request)

    {

        foreach ($this->except as $except) {

            if ($except !== '/') {

                $except = trim($except, '/');

            }

            if ($request->fullUrlIs($except) || $request->is($except)) {

                return true;

            }

        }

        return false;

    }

此时将form表单中的csrf删除,再提交表单,并不会触发419错误:


array:2 [▼

  "name" => "123"

  "password" => "123"

]

疑问一:VerifyCsrfToken是如何被调用的呢?

打开RouteServiceProvider.php,此文件中加载了routes中的路由文件,针对web的代码如下 :


public function map()

    {

        $this->mapApiRoutes();

        $this->mapWebRoutes();

        //

    }

    /**

    * Define the "web" routes for the application.

    *

    * These routes all receive session state, CSRF protection, etc.

    *

    * @return void

    */

    protected function mapWebRoutes()

    {

        // 在访问这个这些路由的时候,就会执行中间件组 web 所对应的中间件:Kernel.php

        Route::middleware('web')

            ->namespace($this->namespace)

            ->group(base_path('routes/web.php'));

    }

疑问二:为什么post需要校验csrf,而get不需要?

在Illuminate\Foundation\Http\Middleware\VerifyCsrfToken.php中可看到如下代码:


public function handle($request, Closure $next)

    {

        if (

            $this->isReading($request) ||

            $this->runningUnitTests() ||      $this->inExceptArray($request) ||

            $this->tokensMatch($request)

        ) {

            return $this->addCookieToResponse($request, $next($request));

        }

        throw new TokenMismatchException;

    }

    /**

    * Determine if the HTTP request uses a ‘read’ verb.

    *

    * @param  \Illuminate\Http\Request  $request

    * @return bool

    */

    protected function isReading($request)

    {

        //此处已判断请求放方式

        return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);

    }

疑问三:前后端分离如何实现?

在Illuminate\Foundation\Http\Middleware\VerifyCsrfToken.php中可看到如下代码:


protected function tokensMatch($request)

    {

        $token = $this->getTokenFromRequest($request);

        return is_string($request->session()->token()) &&

              is_string($token) &&

              hash_equals($request->session()->token(), $token);

    }

    /**

    * Get the CSRF token from the request.

    *

    * @param  \Illuminate\Http\Request  $request

    * @return string

    */

    protected function getTokenFromRequest($request)

    {

        $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

        if (! $token && $header = $request->header('X-XSRF-TOKEN')) {

            $token = $this->encrypter->decrypt($header, static::serialized());

        }

        return $token;

    }

通过上面代码可看到token可从表单中获取亦可从header中获取。前后端分离基本上都是通过在header中追加X-XSRF-TOKEN。

相关文章

网友评论

      本文标题:laravel中csrf验证

      本文链接:https://www.haomeiwen.com/subject/lngwghtx.html