美文网首页
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