在Laravel中处理过期的令牌

时间:2015-07-16 08:44:39

标签: php laravel laravel-5

在laravel 5中处理过期令牌的最佳方法是什么。

我的意思是我有一个页面,它有一些执行ajax请求的链接。当页面加载时它们工作正常,但是当我等待一段时间后,我得到一个TOKEN MISMATCH错误。

现在,我必须刷新页面才能让它再次运行。但是,我不想刷新页面。我想要一些方法来刷新令牌或其他一些工作来解决它。

我希望你明白我的观点。

14 个答案:

答案 0 :(得分:18)

我为这种情况组合了两件事:

<强> 1。增加会话生存期

//In config/session.php replace this:

'lifetime' => 120

//with:

'lifetime' => 360

Laravel 5默认生命周期为120(分钟),您可以将其更改为您喜欢的任何值,例如360(6小时)

<强> 2。捕获异常并显示错误消息

//In app/Exceptions/Handler.php replace this:

public function render($request, Exception $e)
{
    if ($e instanceof ModelNotFoundException) {
        $e = new NotFoundHttpException($e->getMessage(), $e);
    }

    return parent::render($request, $e);
}

//with:

public function render($request, Exception $e)
{
    if ($e instanceof ModelNotFoundException) {
        $e = new NotFoundHttpException($e->getMessage(), $e);
    }

    if ($e instanceof \Illuminate\Session\TokenMismatchException) {            
        return redirect('/')->withErrors(['token_error' => 'Sorry, your session seems to have expired. Please try again.']);
    }

    return parent::render($request, $e);
}

所以基本上你将用户重定向到root&#34; /&#34; (您可以将此更改为您想要的任何路径)并显示错误消息,并且在该页面上您必须执行此操作以显示错误消息:

@if ($errors->has('token_error'))
    {{ $errors->first('token_error') }}
@endif

答案 1 :(得分:17)

解决这个问题的方法是,每隔一定时间实际获取新令牌,否则你就是在挫败csrf令牌的目的:

<html>
    <head>
        <meta name="csrf_token" content="{{ csrf_token() }}">
    </head>
    <body>
        <script type="text/javascript">
            var csrfToken = $('[name="csrf_token"]').attr('content');

            setInterval(refreshToken, 3600000); // 1 hour 

            function refreshToken(){
                $.get('refresh-csrf').done(function(data){
                    csrfToken = data; // the new token
                });
            }

            setInterval(refreshToken, 3600000); // 1 hour 

        </script>
    </body>
</html>

在laravel路线

Route::get('refresh-csrf', function(){
    return csrf_token();
});

我在语法错误的情况下道歉,很长时间没有使用jquery,但我想你明白了这个想法

答案 2 :(得分:16)

我认为@UX Labs的回答是误导性的。 然后@jfadich的评论似乎完全不正确。

2017年5月,对于Laravel 5.4,我用这种方式解决了这个问题:

这是一个有效的答案

web.php

Route::post('keep-token-alive', function() {
    return 'Token must have been valid, and the session expiration has been extended.'; //https://stackoverflow.com/q/31449434/470749
});

在您的观点中使用javascript:

$(document).ready(function () {

    setInterval(keepTokenAlive, 1000 * 60 * 15); // every 15 mins

    function keepTokenAlive() {
        $.ajax({
            url: '/keep-token-alive', //https://stackoverflow.com/q/31449434/470749
            type: 'post',
            headers: {
                'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
            }
        }).then(function (result) {
            console.log(new Date() + ' ' + result + ' ' + $('meta[name="csrf-token"]').attr('content'));
        });
    }

});

请注意,'keep-token-alive'中的排除项中必须列出VerifyCsrfToken.php。正如@ ITDesigns.eu在评论中暗示的那样,对于此路由来说,验证当前是否存在有效令牌并且只需要延长其到期时间非常重要。

为什么这种方法解决了我的问题

我的Laravel网站允许用户观看视频(一小时),并且它使用ajax每分钟发布一次进度。

但许多用户加载页面,然后在几个小时之后才开始播放视频。

我不知道他们为什么在观看之前就把他们的浏览器标签打开了,但他们确实如此。

然后我会在我的日志中获得大量的TokenMismatch异常(并且会错过他们进度的数据)。

session.php中,我将'lifetime'从120分钟更改为360分钟,但这仍然不够。而且我不想让它超过6个小时。所以我需要启用这一页来经常通过ajax扩展会话。

如何测试它并了解令牌的工作原理:

web.php

Route::post('refresh-csrf', function() {//Note: as I mentioned in my answer, I think this approach from @UX Labs does not make sense, but I first wanted to design a test view that used buttons to ping different URLs to understand how tokens work. The "return csrf_token();" does not even seem to get used.
    return csrf_token();
});
Route::post('test-csrf', function() {
    return 'Token must have been valid.';
});

在您的观点中使用javascript:

<button id="tryPost">Try posting to db</button>
<button id="getNewToken">Get new token</button>

(function () {
    var $ = require("jquery");

    $(document).ready(function () {
        $('body').prepend('<div>' + new Date() + ' Current token is: ' + $('meta[name="csrf-token"]').attr('content') + '</div>');
        $('#getNewToken').click(function () {
            $.ajax({
                url: '/refresh-csrf',
                type: 'post',
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                }
            }).then(function (d) {
                $('meta[name="csrf-token"]').attr('content', d);
                $('body').prepend('<div>' + new Date() + ' Refreshed token is: ' + $('meta[name="csrf-token"]').attr('content') + '</div>');
            });
        });
        $('#tryPost').click(function () {
            $.ajax({
                url: '/test-csrf',
                type: 'post',
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                }
            }).then(function (d) {
                $('body').prepend('<div>' + new Date() + ' Result of test: ' + d + '</div>');
            });
        });


    });
})();

session.php中,暂时将'lifetime'更改为非常短的内容以用于测试目的。

然后玩。

这就是我学习Laravel令牌如何工作的方式,以及我们如何经常成功地POST到受CSRF保护的路由,以便令牌继续有效。

答案 3 :(得分:2)

我有一个简单的解决方案:

  • 不需要您延长会话寿命。
  • 打开多个选项卡。
  • 如果会话由于设备关闭而超时,也可以使用。

在/routes/web.php中:

$router->get('csrf-token', function() {
   return request()->session()->token();
});

这只是返回当前的csrf令牌。

  • 如果在调用此路由时令牌无效(例如,当设备长时间关闭时),它将返回一个新的令牌,该令牌是通过启动会话创建的。
  • 如果仍然有一个有效的令牌,它将被返回。由于调用此路由将延长会话,因此令牌的生存期也得到延长。

因为这仅在必要时返回新令牌,所以按@Adam所述打开多个选项卡不会有问题。

您只需要确保每X分钟调用一次上述路由(其中X是您的会话生存期-5分钟),并更新所有_token输入。我这样做如下(我在这里使用momentjs和axios):

handleNewCsrfToken();

// Use visbility API to make sure the token gets updated in time, even when the device went to sleep.
document.addEventListener('visibilitychange', function() {
    if (document.visibilityState === 'visible') {
        setTimeoutToRefreshCsrfToken();
    } else if (document.visibilityState === 'hidden') {
        clearTimeout(refreshCsrfTokenTimeout);
    }
});

function handleNewCsrfToken() {
    updateCsrfTokenTimeoutTarget();
    setTimeoutToRefreshCsrfToken();
}

function updateCsrfTokenTimeoutTarget() {
    csrfTokenTimeoutTarget = moment().add(2, 'hour').subtract(5, 'minute');
}

function setTimeoutToRefreshCsrfToken() {
    refreshCsrfTokenTimeout = setTimeout(refreshCsrfToken, csrfTokenTimeoutTarget.diff());
}

function refreshCsrfToken() {
    axios.get('/csrf-token').then(function(response) {
        document.getElementsByName('_token').forEach(function(element) {
            element.value = response.data;

            handleNewCsrfToken();
        });
    });
}

答案 4 :(得分:1)

增加会话的lifetime。您可以通过编辑laravel配置中的config/session.php文件来完成此操作。

/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to immediately expire on the browser closing, set that option.
|
*/

'lifetime' => 120,

答案 5 :(得分:1)

处理此异常的最佳方法是使用timestamp location 02JAN19:00:00:00 home 02JAN19:05:00:00 work1 02JAN19:06:00:00 work2 02JAN19:07:00:00 home

timestamp location duration (in Minutes)
02JAN19:06:00:00 work2    119

,以及要显示此消息的位置(在您所有包含App\Exceptions\Handler.php的页面中),请添加以下内容:

public function render($request, Exception $e) {

        if ($e instanceof \Illuminate\Session\TokenMismatchException) {            
            return Redirect::back()->withErrors(['session' => 'Désolé, votre session semble avoir expiré. Veuillez réessayer.']);
        }

        return parent::render($request, $e);
    }


答案 6 :(得分:1)

在主布局文件中尝试

@guest
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    <meta http-equiv="refresh" content="{{config('session.lifetime') * 60}}">
@endguest

答案 7 :(得分:0)

我知道这不是最好的解决方案,但却是最简单的解决方案之一。 只需为该页面禁用CSRF保护,用户需要花费大量时间。 例如,在我的网站上,他们可以在一个页面上写几个小时的文章。如果由于CSRF保护而无法保存文章,那将是非常令人沮丧的。

答案 8 :(得分:0)

我认为最好的选择是使用config / session.php文件的生命周期配置,然后在javascript代码中将生命周期值乘以60 * 1000。使用laravel提供的辅助函数config(),它可能如下所示:

<script type="text/javascript">
    var timeout = ({{config('session.lifetime')}} * 60) * 1000;
    setTimeout(function() {
        //reload on current page
        window.location = '';
    }, timeout);
</script>

答案 9 :(得分:0)

这些都是我不喜欢的解决方法..(但我承认它们可以工作) 我不知道,因为巫婆版本存在于Laravel,但有一种方法可以从CSRF令牌验证中排除页面:

https://laravel.com/docs/5.5/csrf

只需在您要排除的uri上的VerifyCsrfToken中间件上添加$ except数组上的记录。 请注意,这只能在特定情况下进行。

这对我来说是最好的解决方案...简单而且就像(几乎)Laravel上的所有内容一样,他们已经考虑过了。 ;)

答案 10 :(得分:0)

一种快速简便的方法... 用于在令牌过期时处理ajax请求:将该脚本添加到主版式或文档的末尾

$(window).load(function(){
    $.ajaxSetup({
        statusCode: {
            419: function(){
                    location.reload(); 
                }
        }
    });
});

要处理令牌过期时的http请求,请在以下路径中创建419.blade.php:\ resources \ views \ errors并将此脚本添加至其中:

<script type="text/javascript">
    //reload on current page
    window.location = '';

</script>

答案 11 :(得分:0)

环行导航令牌通常被认为是一种糟糕的方法,但是使用上述js计时器也存在问题。当浏览器选项卡没有聚焦,最小化或者对于许多用户而言,笔记本电脑/设备处于睡眠/关闭状态时,js seetTimeout / setInterval将不可靠。

一个更好的方法可能是使用js计时器从cookie(或对于挑剔的GDPR no-cookie用户的元标记)中设置的日期戳中重新计算“死亡时间”。此日期戳将是会话将终止的真实世界(时区)时间,并在每次页面刷新时更新。这样,您不在时浏览器/设备在做什么/不做什么都没关系,对于那些“保持登录状态”等的人来说还是很准确的。

接下来的问题是怎么做,而不是自动刷新令牌-向用户提供“重新登录”格式(模式/弹出式),如上所述,该新方法将新令牌取消到页面。

答案 12 :(得分:0)

您可以尝试Caffeine for Laravel package来设置时间间隔,然后像某些答案中所建议的那样刷新令牌,并且它将以具有csrf令牌的每种形式自动添加

答案 13 :(得分:0)

根据docs

  

Laravel为每个活跃用户自动生成CSRF“令牌”   会话由应用程序管理。

这意味着,对于任何个人,csrf代码对于用户访问的任何页面都是相同的。会话期满后,它将变为无效。因此,如果将生存期设置为50年,CSRF令牌将仅在50年后过期,这对我来说似乎可以接受。 (我无法想象有人打开了50年未使用的标签页,但您永远不会知道。)

这可以在config/session.php中实现:

 /*
    |--------------------------------------------------------------------------
    | Session Lifetime
    |--------------------------------------------------------------------------
    |
    | Here you may specify the number of minutes that you wish the session
    | to be allowed to remain idle before it expires. If you want them
    | to immediately expire on the browser closing, set that option.
    |
    */

    'lifetime' => 60 * 24 * 365 * 50, // Set session lifetime to 50 year

    'expire_on_close' => true,

为什么我不喜欢上述任何答案:

  1. UX Labs的答案

使会话永久存在,并在固定时间后重新创建新的CSRF token。如果用户打开多个水龙头,则会出现问题。每次轻按一次刷新CSRF令牌,其他所有选项卡均无效。

  1. 来自瑞安的答案

此答案更好,因为它不会更改CSRF token,因此不会影响多个选项卡。通过使用setInterval在固定时间后进行js调用,可以使会话保持活跃状态​​。但是,setInterval在PC进入睡眠状态时不起作用。因此,当PC进入睡眠状态时,会话可能会过期,这也是一种很可能的情况。因此,我不会尝试通过js调用使会话保持活动状态,而是将生存期延长至50年。

  1. 来自paulalexandru的答案

在会话超时时显示错误是可以的,但是最好是永远不要发生此问题。将生存期设置为6h是不够的,因为它很可能会打开一个选项卡几天。

  1. 其他答案

所有其他答案都建议对有问题的路由禁用CSRF,但这当然是不可行的,因为它会带来很大的安全风险。