Laravel 前後端分離 csrf 防護

自由飛發表於2018-05-24

首先根據文件詳解csrf的流程機制,然後分析下怎麼在前後端分離的情況下進行csrf防護?
1.首先介紹token的生成,在 Illuminate\Session\Store.php 中start() 方法中,然後點選方法regenerateToken()方法中:

public function start()
{
    $this->loadSession();
    if (! $this->has('_token')) {
        $this->regenerateToken();  
    }
    return $this->started = true;
}

    public function regenerateToken()
{
    $this->put('_token', Str::random(40));
}

可見token是每次載入session時自動存入進session中。
2.然後看前臺獲取token值,首先表單中 {{csrf_token()}} 點入進去可以看到

function csrf_field()
{
    return new HtmlString('<input type="hidden" name="_token" value="'.csrf_token().'">'); //點入進去csrf_token方法中
}
function csrf_token()
{
    $session = app('session');
    if (isset($session)) {
        return $session->token();
    }
    throw new RuntimeException('Application session store not set.');
}

可以看到前臺token值是從session儲存的token值中獲取到的。
3.然後看中介軟體是怎麼驗證前臺提交的_token值:
在VerifyCsrfToken.php中繼承Middleware類,點選進入類中檢視handle方法

public function handle($request, Closure $next)
{
    if (
        $this->isReading($request) ||    //驗證請求方式,如果不在陣列['HEAD', 'GET', 'OPTIONS']中 返回false;
        $this->runningUnitTests() ||      //檢視程式是否在進行測試 正式執行返回false;
        $this->inExceptArray($request) ||  //檢視$except(白名單,不用驗證_token)陣列,如果請求路徑在陣列中返回true; 
        $this->tokensMatch($request)   //進行token驗證
    ) {
        return $this->addCookieToResponse($request, $next($request));
    }

    throw new TokenMismatchException;
}

我們點選$this->tokensMatch()方法中可以看到請求中的token和session中的token是否相等:

    protected function tokensMatch($request)
{
    $token = $this->getTokenFromRequest($request); //從請求中獲取_token值

    return is_string($request->session()->token()) &&
           is_string($token) &&
           hash_equals($request->session()->token(), $token);  //session中的token和請求中的token是否相等;
}

點選進入$this->getTokenFromRequest() 方法中

 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);
    }

    return $token;
}  

從中可以看到獲取請求中token值的幾種方法:
from表單、頭部值X-CSRF-TOKEN、頭部值X-XSRF-TOKEN 這幾種方試,只要明白了中介軟體怎麼接收token值進行判斷,然後前臺就那麼幾種傳值方式了
第一種 form表單傳值 {{ csrf_field() }}或ajaxpost攜帶值 對應 $request->input('_token')
第二種 header頭傳值:

     <meta name="csrf-token" content="{{ csrf_token() }}">
     $.ajaxSetup({   //修改jquery庫中程式碼
     headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
      }
 });

如果你用vue,只需將 resources/assets/js/bootstrap.js 引入你的檔案中 然後 meta標籤頭部值放入總模板裡,每次axios請求都會攜帶X-CSRF-TOKEN進入中介軟體進行驗證,下面為js檔案:

let token = document.head.querySelector('meta[name="csrf-token"]');

if (token) {
     window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
 } else {
console.error('CSRF token not found: https://learnku.com/docs/laravel/csrf#csrf-x-csrf-token');
}       

至此 從token值生成到前端攜帶token到後臺驗證已經完全走完一個流程了,接下來想一下假如前後端分離情況下怎麼進行token驗證
很簡單每次提交前用api的形式獲取token值傳入前端(csrf_token()獲取token值),然後前端用header頭形式或ajax形式傳入過來就可以了。
如果感覺有用就贊一下唄!!!

自由飛

相關文章