2015-02-13 27 views
13

Gần đây tôi đã di chuyển sang Laravel 5 và bây giờ, kiểm tra CSRF là trên mọi bài đăng. Tôi nghĩ về việc loại bỏ nó nhưng tôi muốn làm theo những thực hành tốt nhất, vì vậy tôi sẽ giữ nó theo cách đó.Trường dữ liệu ẩn mã toàn cục của Laravel 5 CSRF cho tất cả các biểu mẫu trong một trang

Mặt khác, tôi gặp sự cố khi gửi yêu cầu ajax .. trang của tôi có nhiều biểu mẫu và một số nội dung gửi không phải từ biểu mẫu, chỉ cần gọi ajax đơn giản. Ý tưởng của tôi là có một đầu vào "mã thông báo" bị ẩn trên trang và đính kèm nó vào mỗi lần gửi. Có bất kỳ hạn chế nào khi có đầu vào mã thông báo đơn phổ quát không?

Ngoài ra, làm cách nào tôi có thể xuất mã thông báo? Nó sẽ là ok để chỉ cần tạo một đầu vào ẩn trên chân trang?

Trả lời

19

Tôi không thấy bất kỳ hạn chế nào. Bạn có thể dễ dàng tạo ra một lĩnh vực thẻ toàn cầu trong file layout của bạn:

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

Hoặc nếu bạn sử dụng xây dựng hình thức:

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

Trong jQuery bạn có thể sử dụng giống như this để gắn thẻ cho mọi yêu cầu.

+0

Tốt, trong L5 nó sẽ là {!! Biểu mẫu :: token() !!} – raphadko

+0

@RaphaelCabrera Có bạn đã đúng. Chết tiệt, tôi sẽ không bao giờ quen với '{!!' ... May mắn thay bạn có thể thay đổi chúng :) – lukasgeiter

+0

Không thể gửi lại biểu mẫu với cùng một mã thông báo sau khi lỗi xác thực – mvladk

1

Bạn cần phải vượt qua tiêu đề X-XSRF-TOKEN có chứa phiên bản được mã hóa của csrf-token.

Có hai cách mà điều này có thể được thực hiện mà tôi biết. Bạn có thể mã hóa mã thông báo và chuyển mã thông báo đó sang chế độ xem:

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

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

Hoặc bạn có thể lấy mã thông báo từ cookie bằng JavaScript (Góc làm cho việc này trở nên dễ dàng).Trong vani JS bạn có thể làm điều gì đó như thế này:

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 
} 

trong jQuery thì bạn có thể làm điều gì đó như thế này cho các yêu cầu ajax:

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

Dưới đây là một số trích đoạn của làm thế nào tôi có CSRF tôi làm việc cho tất cả các kịch bản khác nhau trong ứng dụng jQuery Mobile của tôi mà gần đây tôi đã nâng cấp để sử dụng Laravel 5:

Tôi đã thêm mã thông báo csrf được mã hóa vào một biến sẽ được chuyển đến chế độ xem của tôi trong bộ điều khiển cơ sở chính của tôi: app\Http\Controllers\MyController.php

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

Sau đó, tôi thêm thẻ meta trong tiêu đề giao diện chính của tôi: resources\views\partials\htmlHeader.blade.php

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

Sau đó, tôi cũng nói thêm đoạn jquery này như đề xuất trong một số diễn đàn:

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

Tuy nhiên, chìa khóa (cho thiết lập của tôi ít nhất) là việc bổ sung kiểm tra cho các XSRF-TOKEN cookie trong phần mềm trung gian VerifyCsrfToken tùy chỉnh của tôi: 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)); 
    } 

Trước khi tôi nói thêm rằng, chỉ là về tất cả các bài AJAX của tôi (bao gồm cả hình thức đệ trình và listviews lazyloading) đã thất bại do một TokenMismatchException.

EDIT: Suy nghĩ thứ hai, tôi không biết cần so sánh mã thông báo phiên với mã được đặt trong cookie (mã này có thể đến từ mã phiên ở vị trí đầu tiên phải không?). Điều đó có thể đã bỏ qua sự an toàn của tất cả.

Tôi nghĩ vấn đề chính của tôi là đoạn mã jquery ở trên được cho là thêm tiêu đề X-XSRF-TOKEN vào mọi yêu cầu ajax. Điều đó không hiệu quả đối với tôi trong ứng dụng jQuery Mobile của tôi (đặc biệt, trong số lazyloader plugin) của tôi cho đến khi tôi thêm một số tùy chọn cho chính plugin đó. Tôi đã thêm một công cụ chọn mặc định mới csrf (có thể là meta[name="_token"] trong trường hợp này) và cài đặt mặc định mới csrfHeaderKey (trong trường hợp này là X-XSRF-TOKEN). Về cơ bản, trong quá trình khởi tạo plugin, thuộc tính mới _headers được khởi tạo bằng mã thông báo CSRF nếu một mã định vị được chọn bởi bộ chọn csrf (mặc định hoặc do người dùng xác định). Sau đó, ở 3 nơi khác nhau mà POST ajax có thể được tắt (khi đặt lại biến phiên hoặc khi tải xuống chế độ xem danh sách), tùy chọn tiêu đề của $ .ajax được đặt bằng bất kỳ nội dung nào trong số _headers.

Dù sao, vì X-XSRF-TOKEN nhận được ở phía máy chủ đến từ siêu dữ liệu được mã hóa _token, tôi nghĩ rằng bảo vệ CSRF hiện đang hoạt động như mong muốn.

app\Http\Middleware\VerifyCsrfToken.php của tôi bây giờ trông như thế này (mà chủ yếu là về việc thực hiện mặc định được cung cấp bởi 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

Tôi nghĩ rằng bạn có thể làm một cái gì đó như thế này (không kiểm tra sẽ cập nhật nếu tôi nhận được một cơ hội)

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

bạn thực sự có thể muốn lưu trữ mã thông báo trong biến mà bạn reupdate khi hết hạn.

Lợi ích của việc thêm nó khi gửi là nếu bạn nối thêm các phần tử qua ajax, tôi nghĩ nó sẽ vẫn hoạt động mà không cần phải thêm bất cứ thứ gì khác.

EDIT: Dưới đây là một bài viết tuyệt vời về việc sử dụng Rails UJS với Laravel (trong đó bao gồm auto này CRSF thẻ chức năng): https://medium.com/@barryvdh/unobtrusive-javascript-with-jquery-ujs-and-laravel-e05f444d3439

13

Có một helper để thêm các hình thức thẻ hình thức bên trong. Bạn chỉ có thể sử dụng

{!! csrf_field() !!} 

bên trong biểu mẫu. Nó sẽ thêm đầu vào bị ẩn và mã thông báo.

+1

như Laravel> = 5.1, bạn không còn cần sử dụng đầu ra thô nữa, bạn có thể sử dụng cú pháp thoát thường xuyên '{{csrf_field()}}' –

2

Bạn có thể sử dụng một cái gì đó như thế này ở dưới cùng của trang:

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

này sẽ nối một đầu vào ẩn để tất cả các bạn forms:

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

Và đối với tất cả các yêu cầu AJAX của bạn:

$.ajaxSetup({ 
    beforeSend: function (xhr, settings) { 
     //////////// Only for your domain 
     if (settings.url.indexOf(document.domain) >= 0) { 
      xhr.setRequestHeader("X-CSRF-Token", "{{csrf_token()}}"); 
     } 
    } 
}); 
Các vấn đề liên quan