Laravel CSRF - 419 页面因向其他网站的发布请求而过期

Posted

技术标签:

【中文标题】Laravel CSRF - 419 页面因向其他网站的发布请求而过期【英文标题】:Laravel CSRF - 419 Page expired with post request to other website 【发布时间】:2020-06-30 18:19:28 【问题描述】:

目前我在 2 个网站上工作。一个网站(网站 A)是一个简单的 CMS,另一个网站(网站 B)是用户的常规网站,用户可以在其中管理个人资料等。用户可以在 CMS 中浏览包。这些包是使用来自其他站点(网站 B)的 AJAX 请求接收的。这些包也可以买。当用户购买包裹(网站 A)时,正在向另一个网站(网站 B)发送一个 post 请求。

所以澄清一下,比如说,我们有网站 A 和 B。当网站 A 上的用户购买包裹时,会向“B.com/package/purchase”发送一个发布请求。这种设计只有一个问题。在 Laravel 中,每个表单都必须发送一个 csrf 令牌。因为我向另一个网站发出 POST 请求,所以我无法从网站 A 生成 CSRF 令牌,因为那样该请求将始终过期。所以我必须使用来自网站 B 的 CSRF 令牌。我已经尝试过了,但请求仍然返回 419 Page Expired 错误。我该如何解决这个问题?

网站 A 上的表单:(此表单位于 javascript 附件中,因此包含引号。

'<form method="POST" action="https://rainierlaansite.test/package/' + el.token +'/purchase" id="purchase-form">' +
    '<input type="hidden" value="'+ el.token +'" name="package_token">' +
    '<input name="_token" type="hidden" id="csrf-token" value=' + data[1] + '>' +
      '<h4>' +
        '<a href="#" id="download_package" class="badge badge-pill '+ (el.price == 0 ? 'badge-primary': 'badge-light') +'" onclick="document.getElementById(\'purchase-form\').submit();">' +
            (el.price == 0 ? 'Download': '$ ' + el.price) +
        '</a>' +
      '</h4>' +
'<form>' +

网站 A 上的 AJAX 请求:

<script>
        $.ajax(
            headers: 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'), 'X-Requested-With': 'XMLHttpRequest',
            url: 'https://www.rainierlaansite.test/api/packages/get',
            type: 'GET',
            data: ,
            success: function(data) 
                $('#package-loading').fadeOut();
                let wrapper = $("#packages-wrapper");
                let popup_list = $('.popup-list');
                let popup = 'popup-id';

                if(data == null) 
                    console.log('lol')
                
                $.each(data[0], function(index, value) 
                    let el = data[0][index];
                    wrapper.append(
                        '<div class="row my-4" data-id='+ el.id +'>' +
                            '<div class="col-2 d-flex justify-content-center"><i class="fad fa-archive fa-2x"></i></div>' +
                            '<div class="col-6">' +
                                '<a href="#" class="popup" data-popup-id="'+ el.id +'"><h6 class="m-0">'+ el.name +'</h6></a>' +
                                '<p class="m-0 sub-text">'+ el.description +'</p>' +
                            '</div>' +
                            '<div class="col-4 d-flex justify-content-end align-items-center">' +
                                '<a href="#" id="download_package" class="badge badge-pill badge-light popup '+ (el.price == 0 ? 'badge-primary': 'badge-light') +'" style="font-size: 14px;" data-popup-id="'+ el.id +'">' + (el.price == 0 ? 'Download': '$ ' + el.price) +'</a>' +
                            '</div>' +
                        '</div>' + '<hr>'
                    );
                    popup_list.append(
                        '<div class="package-popup shadow popup-'+ el.id +'">' +
                            '<div class="package-popup-dialog animated zoomIn show faster">' +
                                '<div class="package-popup-content">' +
                                    '<div class="row">' +
                                        '<div class="col-2">' +
                                            '<div class="row">' +
                                                '<div class="col-12 d-flex justify-content-center">' +
                                                    '<i class="fad fa-archive fa-3x"></i>' +
                                                '</div>' +
                                            '</div>' +
                                        '</div>' +
                                        '<div class="col-7">' +
                                            '<div class="row">' +
                                                '<div class="col-12">' +
                                                    '<h4 class="m-0">' + el.name + '</h4>' +
                                                    '<p class="sub-text">' + el.creator + '</p>' +
                                                '</div>' +
                                            '</div>' +
                                        '</div>' +
                                        '<div class="col-3 text-center">' +
                                            '<form method="POST" action="https://rainierlaansite.test/package/' + el.token +'/purchase" id="purchase-form">' +
                                                '<input type="hidden" value="'+ el.token +'" name="package_token">' +
                                                '<input name="_token" type="hidden" id="csrf-token" value=' + data[1] + '>' +
                                                '<h4>' +
                                                    '<a href="#" id="download_package" class="badge badge-pill '+ (el.price == 0 ? 'badge-primary': 'badge-light') +'" onclick="document.getElementById(\'purchase-form\').submit();">' +
                                                    (el.price == 0 ? 'Download': '$ ' + el.price) +
                                                    '</a>' +
                                                '</h4>' +
                                            '<form>' +
                                        '</div>' +
                                        '<div class="offset-2 col-7">' +
                                            '<div class="rating">' +
                                                '<span><i class="fas fa-star yellow"></i></span>' +
                                                '<span><i class="fas fa-star yellow"></i></span>' +
                                                '<span><i class="fas fa-star yellow"></i></span>' +
                                                '<span><i class="fas fa-star yellow"></i></span>' +
                                                '<span><i class="fas fa-star grey"></i></span>' +
                                                '<small><a href="">· Uit 300 beoordelingen</a></small>' +
                                            '</div>' +
                                            '<small>Nog geen beoordelingen</small>' +
                                        '</div>' +
                                        '<div class="col-3 text-center">'+
                                            '<i class="far fa-download"></i> ' + el.downloads +
                                        '</div>' +
                                    '</div>' +
                                    '<div class="row my-5">' +
                                        '<div class="col-12">' +
                                            '<nav>' +
                                                '<div class="nav nav-tabs" id="nav-tab" role="tablist">' +
                                                    '<a class="nav-item nav-link active" id="nav-home-tab" data-toggle="tab" href="#nav-home" role="tab" aria-controls="nav-home" aria-selected="true">Details</a>' +
                                                    '<a class="nav-item nav-link" id="nav-profile-tab" data-toggle="tab" href="#nav-profile" role="tab" aria-controls="nav-profile" aria-selected="false">Reviews</a>' +
                                                    '<a class="nav-item nav-link" id="nav-contact-tab" data-toggle="tab" href="#nav-contact" role="tab" aria-controls="nav-contact" aria-selected="false">Version history</a>' +
                                                '</div>' +
                                            '</nav>' +
                                            '<div class="tab-content" id="nav-tabContent">' +
                                                '<div class="tab-pane fade show active py-4" id="nav-home" role="tabpanel" aria-labelledby="nav-home-tab">' +
                                                    '<p>' + el.description + '</p>' +
                                                '</div>' +
                                                '<div class="tab-pane fade py-4" id="nav-profile" role="tabpanel" aria-labelledby="nav-profile-tab">' +
                                                    'Dit is twee tekst' +
                                                '</div>' +
                                                '<div class="tab-pane fade py-4" id="nav-contact" role="tabpanel" aria-labelledby="nav-contact-tab">' +
                                                    'Dit is tekst 3' +
                                                '</div>' +
                                            '</div>' +
                                        '</div>' +
                                    '</div>' +
                                '</div>' +
                            '</div>' +
                        '</div>'
                    );
                );

                $('.popup').on('click', function () 
                    if($(this).data(popup)) 
                        let item = $('.popup-' + $(this).data(popup));
                        item.addClass('show');
                        $(item).on('click', function(event) 
                            if (event.target.classList.contains("shadow")) 
                                item.removeClass('show');
                            
                        )
                    
                );
            ,
            error: function(e) 
                $('#package-error').fadeIn().text("Unfortunately there was an error retrieving the packages");
            
        );
    </script>

AJAX 请求接收到的内容

 public function get()
    
        $csrf = json_encode(csrf_token());
        return response()
            ->json([Package::all(),  $csrf]);
    

【问题讨论】:

【参考方案1】:

Laravel 可以轻松保护您的应用程序免受跨站请求伪造 (CSRF) 攻击。跨站点请求伪造是一种恶意利用,代表经过身份验证的用户执行未经授权的命令。

Laravel 自动为应用程序管理的每个活动用户会话生成一个 CSRF“令牌”。此令牌用于验证经过身份验证的用户是实际向应用程序发出请求的用户。

默认情况下,laravel 使用 CSRF 令牌阻止跨站点请求。 如果您愿意,可以通过将 URI 添加到 VerifyCsrfToken 中间件中的 except 数组来避免 CSRF 验证。

在https://www.rainierlaansite.test应用的App\Http\Middleware中设置VerifyCsrfToken Middleware如下

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware

    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'api/packages/get',
    ];

【讨论】:

我尝试将其添加到中间件,但如果我尝试提交线程中所述的表单,它仍然会给我一个 419。我还将package/token/purchase 添加到列表中,但它的作用相同。

以上是关于Laravel CSRF - 419 页面因向其他网站的发布请求而过期的主要内容,如果未能解决你的问题,请参考以下文章

Laravel ajax请求419错误及解决办法(CSRF验证) 阿星小栈

419 页面使用 laravel sanctum 过期

Vue + Laravel sanctum CSRF 令牌不匹配 419 错误

Laravel 7 Vue 2 Sanctum 登录错误 419; CSRF 令牌不匹配

Laravel 5.6 上的 419 Ajax 错误 - 已编辑

Laravel 419 错误-VerifyCsrfToken 问题