2015-02-13 8 views
13

Recentemente ho migrato a Laravel 5, e ora il controllo CSRF è su ogni invio di post. Ho pensato di rimuoverlo ma voglio seguire le migliori pratiche, quindi continuerò così.Laravel 5 campo nascosto globale token CSRF per tutti i moduli in una pagina

D'altra parte, sto riscontrando problemi nell'invio di richieste Ajax. La mia pagina ha più moduli e alcune presentazioni non sono nemmeno da moduli, solo semplici chiamate ajax. La mia idea è di avere un singolo "token" nascosto nella pagina e collegarlo a ogni invio. Ci sono degli svantaggi nell'avere quell'ingresso di token universale singolo?

Inoltre, come posso emettere il token? Sarebbe giusto solo creare un input nascosto sul piè di pagina?

risposta

19

Non vedo alcun inconveniente. È possibile creare facilmente un campo token globale nel file di layout:

<input type="hidden" name="_token" id="csrf-token" value="{{ Session::token() }}" /> 

Oppure, se si utilizza il costruttore modulo:

{!! Form::token() !!} 

In jQuery si potrebbe usare qualcosa come this per attaccare il token ad ogni richiesta

+0

Nizza, in L5 sarebbe {!! Form :: token() !!} – raphadko

+0

@RaphaelCabrera Sì hai ragione. Accidenti, non mi abituerò mai a '{!!' ... Fortunatamente puoi cambiarli :) – lukasgeiter

+0

Non riesci a inviare di nuovo il modulo con lo stesso token dopo l'errore di validazione – mvladk

1

È necessario passare l'intestazione X-XSRF-TOKEN che contiene una versione crittografata dello csrf-token.

Ci sono due modi in cui ciò può essere fatto di cui sono a conoscenza. È possibile crittografare il token e passarlo insieme alla vista:

$xsrfToken = app('Illuminate\Encryption\Encrypter')->encrypt(csrf_token()); 

return view('some.ajax.form.view')->with('xsrf_token', $xsrfToken); 

Oppure si può prendere il gettone da cookie utilizzando JavaScript (angolare rende questo facile).In JS vaniglia si potrebbe fare qualcosa di simile:

function getCookie(name) { 
    var pattern = RegExp(name + "=.[^;]*") 
    matched = document.cookie.match(pattern) 
    if (matched) { 
     var cookie = matched[0].split('=') 
     return decodeURIComponent(cookie[1]) 
    } 
    return false 
} 

In jQuery si potrebbe poi fare qualcosa di simile per la richiesta ajax:

$.ajax({ 
    // your request 
    // 
    beforeSend: function(request) { 
     return request.setRequestHeader('X-XSRF-TOKEN', getCookie('XSRF-TOKEN')); 
    } 
}); 
3

Ecco alcuni estratti di come ho ottenuto il mio CSRF lavorare per tutti i diversi scenari nella mia richiesta jQuery mobile che ho recentemente aggiornato a utilizzare laravel 5:

ho aggiunto un token CSRF cifrato in una variabile che verrà passato ai miei punti di vista nel mio controller principale base: app\Http\Controllers\MyController.php

$this->data['encrypted_csrf_token'] = Crypt::encrypt(csrf_token()); 

Poi, ho aggiunto il meta tag a mio avviso principale intestazione: resources\views\partials\htmlHeader.blade.php

<meta name="_token" content="{!! $encrypted_csrf_token !!}"/> 

Poi, ho anche aggiunto questo jquery frammento come suggerito in alcuni forum:

$(function() { 
      $.ajaxSetup({ 
        headers: { 
          'X-XSRF-TOKEN': $('meta[name="_token"]').attr('content') 
        } 
      }); 
    }); 

Ma, la chiave (almeno per il mio setup) era l'aggiunta del controllo per il cookie XSRF-TOKEN nel mio middleware VerifyCsrfToken personalizzato: app\Http\Middleware\VerifyCsrfToken.php:

/** 
    * Determine if the session and input CSRF tokens match. 
    * 
    * @param \Illuminate\Http\Request $request 
    * @return bool 
    */ 
    protected function tokensMatch($request) 
    {  
      $token = $request->session()->token(); 

      $header = $request->header('X-XSRF-TOKEN'); 

      $cookie = $request->cookie('XSRF-TOKEN'); 

      return StringUtils::equals($token, $request->input('_token')) || 
        ($header && StringUtils::equals($token, $this->encrypter->decrypt($header))) || 
        ($cookie && StringUtils::equals($token, $cookie)); 
    } 

Prima ho aggiunto che, quasi tutti i miei post AJAX (tra cui l'invio di moduli ea elenco lazyloading) sono insufficienti a causa di una TokenMismatchException.

EDIT: Il secondo pensiero, io non sono sicuro di quanto senso abbia per confrontare il token di sessione rispetto a quella impostata nel cookie (che sarebbe venuto dal token di sessione, in primo luogo giusto?). Questo potrebbe aver semplicemente scavalcato la sicurezza di tutto questo.

Penso che il mio problema principale fosse con il frammento jquery al di sopra del quale si supponeva fosse l'aggiunta dell'intestazione X-XSRF-TOKEN ad ogni richiesta Ajax. Questo non funzionava per me nella mia app in jQuery Mobile (nello specifico, nel mio lazyloader plugin) finché non ho aggiunto alcune opzioni per il plugin stesso. Ho aggiunto un nuovo selettore predefinito csrf (che in questo caso sarebbe meta[name="_token"]) e una nuova impostazione predefinita csrfHeaderKey (che in questo caso sarebbe X-XSRF-TOKEN). Fondamentalmente, durante l'inizializzazione del plug-in, una nuova proprietà _headers viene inizializzata con il token CSRF se ne è disponibile uno dal selettore csrf (predefinito o definito dall'utente). Quindi, nei 3 diversi posti in cui un POST ajax può essere sparato (quando si ripristinano le variabili di sessione o quando si esegue il mirroring di una listview) l'opzione di intestazioni di $ .ajax viene impostata con qualsiasi cosa sia in _headers.

In ogni caso, poiché il X-XSRF-TOKEN ricevuto sul lato server proviene dal meta _token crittografato, penso che la protezione CSRF funzioni ora come dovrebbe.

mio app\Http\Middleware\VerifyCsrfToken.php ora assomiglia a questo (che è essenzialmente di nuovo alla implementazione di default fornito da laravel 5 - LOL):

/** 
    * Determine if the session and input CSRF tokens match. 
    * 
    * @param \Illuminate\Http\Request $request 
    * @return bool 
    */ 
    protected function tokensMatch($request) 
    { 
      $token = $request->session()->token(); 

      $_token = $request->input('_token'); 

      $header = $request->header('X-XSRF-TOKEN'); 

      return StringUtils::equals($token, $_token) || 
        ($header && StringUtils::equals($token, $this->encrypter->decrypt($header))); 
    } 
1

Penso che si possa fare qualcosa di simile (non testato aggiornerà se ottengo una possibilità)

$(document).on('submit', 'form', function(e) 
     $(this).append('<input name="_token" value="{{{ Session::token() }}}">); 
}); 

in realtà potrebbe desiderare di memorizzare gettone in una variabile che si reupdate come scadenza.

Il vantaggio di aggiungendo su submit è se si aggiunge elementi tramite la tecnologia AJAX Penso che sarà ancora lavorare senza dover aggiungere altro.

EDIT: Ecco un grande articolo sull'utilizzo di Rails UJS con laravel (che include questo auto CRSF gettone funzionalità): https://medium.com/@barryvdh/unobtrusive-javascript-with-jquery-ujs-and-laravel-e05f444d3439

13

C'è un helper per aggiungere il modulo di forme all'interno del token. È possibile utilizzare semplicemente

{!! csrf_field() !!} 

all'interno dei moduli. Aggiungerà l'input nascosto e il token.

+1

a partire da Laravel> = 5.1, non hai più bisogno di usare l'output non elaborato, puoi usare la normale sintassi di escape * {{csrf_field()}} ' –

2

Si può usare qualcosa di simile in fondo alla pagina:

$('form').append('{{csrf_field()}}'); 

Questo aggiungerà un ingresso nascosto a tutti i vostri forms:

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

E per ogni tua richiesta AJAX:

$.ajaxSetup({ 
    beforeSend: function (xhr, settings) { 
     //////////// Only for your domain 
     if (settings.url.indexOf(document.domain) >= 0) { 
      xhr.setRequestHeader("X-CSRF-Token", "{{csrf_token()}}"); 
     } 
    } 
}); 
Problemi correlati