php 在 Laravel 中处理过期的令牌

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31449434/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 22:26:31  来源:igfitidea点击:

Handling expired token in Laravel

phplaravellaravel-5

提问by Jamal Abdul Nasir

What is the best way to handle expired tokens in laravel 5.

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

I mean I have a page and it has some links which perform ajax requests. They work fine when the page is loaded but when I wait for sometime then I get a TOKEN MISMATCH error.

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

Now, I have to refresh the page to make it work again. BUT, I don't want to refresh the page. I want some way to refresh the token or some other work around to make it fix.

现在,我必须刷新页面以使其再次工作。但是,我不想刷新页面。我想要一些方法来刷新令牌或其他一些解决方法来修复它。

I hope you got my point.

我希望你明白我的意思。

采纳答案by UX Labs

a work around for it, is to actually get the new token every certain time, otherwise you are defeating the purpose of the csrf token:

解决它的方法是在每个特定时间实际获取新令牌,否则您将违背 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>

In laravel routes

在 Laravel 路线中

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

I apologize in case of any syntax errors, haven't used jquery for long time, but i guess you get the idea

如果出现任何语法错误,我深表歉意,很久没有使用 jquery,但我想你明白了

回答by Ryan

I think the answer by @UX Labs is misleading. And then the comment from @jfadich seems completely incorrect.

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

For Laravel 5.4 in May 2017, I solved the problem this way:

对于 2017 年 5 月的 Laravel 5.4,我是这样解决问题的:

Here Is an Answer That Works

这是一个有效的答案

In web.php:

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

In javascript in your view:

在您看来,在 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
            method: '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'));
        });
    }

});

Note that you must notlist 'keep-token-alive'in the exclusions within VerifyCsrfToken.php. As @ITDesigns.eu implied in a comment, it's important for this route to verify that there is a valid token currentlyand that it just needs to have its expiration extended.

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

Why this approach solves my problem

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

My Laravel site allows users to watch a video (an hour long), and it uses ajax to post their progress every minute.

我的 Laravel 站点允许用户观看视频(一个小时),它每分钟使用 ajax 发布他们的进度。

But many users load the page and then don't start the video until many hours later.

但是许多用户加载页面然后直到几个小时后才开始播放视频。

I don't know why they leave their browser tab open so long before watching, but they do.

我不知道他们为什么在观看之前让浏览器标签打开这么久,但他们确实这样做了。

And then I'd get a ton of TokenMismatch exceptions in my logs (and would miss out on the data of their progress).

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

In session.php, I changed 'lifetime'from 120 to 360 minutes, but that still wasn't enough. And I didn't want to make it longer than 6 hours. So I needed to enable this one page to frequently extend the session via ajax.

在 中session.php,我'lifetime'从 120 分钟更改为 360 分钟,但这还不够。我不想让它超过 6 个小时。所以我需要启用这个页面来频繁地通过ajax扩展会话。

How you can test it and get a sense for how the tokens work:

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

In web.php:

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

In javascript in your view:

在您看来,在 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',
                method: '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',
                method: 'post',
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                }
            }).then(function (d) {
                $('body').prepend('<div>' + new Date() + ' Result of test: ' + d + '</div>');
            });
        });


    });
})();

In session.php, temporarily change 'lifetime'to something very short for testing purposes.

在 中session.php'lifetime'出于测试目的暂时更改为非常简短的内容。

Then play around.

然后随便玩玩。

This is how I learned how the Laravel token works and how we really just need to successfully POST to a CSRF-protected route frequently so that the token continues to be valid.

这就是我了解 Laravel 令牌如何工作的方式,以及我们如何真正需要频繁地成功 POST 到受 CSRF 保护的路由,以便令牌继续有效。

回答by paulalexandru

I combine 2 things for this case:

对于这种情况,我结合了两件事:

1. Increase session lifetime

1.增加会话寿命

//In config/session.php replace this:

'lifetime' => 120

//with:

'lifetime' => 360

Laravel 5 default lifetime is 120 (minutes), you can change it to whatever value you like, for example 360 (6 hours)

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

2. Catch the exception and display an error message

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

So basicaly you redirect the user to the root "/" (you can change this to any path you want) with an error message and on that page you have to do this to display the error message:

因此,基本上您将用户重定向到根“/”(您可以将其更改为您想要的任何路径)并显示错误消息,并且在该页面上您必须执行此操作以显示错误消息:

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

回答by fatemeh sadeghi

a short and fast way.... for handling ajax requests,when token expire : add this script to the end of master layout or your document

一种简短而快速的方法.... 用于处理 ajax 请求,当令牌过期时:将此脚本添加到主布局或文档的末尾

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

and for handling http requests when token expires, create 419.blade.php in this path: \resources\views\errors and add this script to it:

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

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

</script>

回答by brahimm

Best way to handle this Exception is with App\Exceptions\Handler.php.

处理此异常的最佳方法是使用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 avtheitroad expiré. Veuillez réessayer.']);
        }

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


and where ever you wanna show this message (in all your pages that contains csrf_token), add this piece:

以及您想在何处显示此消息(在包含 的所有页面中csrf_token),添加以下内容:

<div>
@if(count($errors)>0)
    @foreach($errors->all() as $error)
        <ul>
            <li>{{$error}}</li>
        </ul>
    @endforeach
@endif
</div>

回答by Adam

According to the docs:

根据文档

Laravel automatically generates a CSRF "token" for each active user session managed by the application.

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

This means, for any individual the csrf code is the same for any page that the user visits. It becomes invalid once your session expires. Thus if you set the lifetime to 1 week, CSRF token will only expire after 1 week.

这意味着,对于任何个人,用户访问的任何页面的 csrf 代码都是相同的。一旦您的会话过期,它就会失效。因此,如果您将生命周期设置为 1 周,CSRF 令牌只会在 1 周后过期。

This can achieved like this in config/session.php:

这可以像这样实现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 * 7, // Set session lifetime to 1 week

    'expire_on_close' => true,


Why I dont like any of the above answers:

为什么我不喜欢以上任何一个答案:

  1. Answer from UX Labs:
  1. 来自 UX 实验室的回答:

Keeps the session forever alive and recreates a new CSRF tokenafter a fixed time. This is an issue if the user has multiple taps open. Everytime one tap refreshes the CSRFtoken, all the other tabs become invalid.

保持会话永远活着并token在固定时间后重新创建一个新的 CSRF 。如果用户打开多个水龙头,这是一个问题。每次点击刷新CSRF令牌时,所有其他选项卡都将无效。

  1. Answer from Ryan
  1. 瑞安的回答

This answer is better, because it does not change the CSRF token, so multiple tabs are not effected. It simply keeps the session alive with making a js call after a fixed time with setInterval. However, setIntervaldoes not work while the PC goes to sleep. So the session may expire when the PC goes to sleep, which is also a likly scenario. Therefore, instead of trying to keep the session alive by js-calls,just increase lifetime.

这个答案更好,因为它不会更改 CSRF token,因此不会影响多个选项卡。它只是在使用setInterval. 但是,setInterval在 PC 进入睡眠状态时不起作用。因此,当 PC 进入睡眠状态时,会话可能会过期,这也是一种可能的情况。因此,与其尝试通过 js 调用保持会话处于活动状态,不如增加生命周期。

  1. Answer from paulalexandru
  1. 来自 paulalexandru 的回答

Displaying an error when session is timed out is okay, but it would be better if the issue never happens. Setting lifetime to 6h is not enough, because its likly that a tab may be open for a couple of days.

当会话超时时显示错误是可以的,但如果问题永远不会发生会更好。将生命周期设置为 6 小时是不够的,因为标签页很可能会打开几天。

  1. Other answers
  1. 其他答案

All the other answers propose to disable CSRF for the route in questions, but this is of course no option, because it creates a big security risk.

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

回答by Thomas Venturini

Increase the lifetimeof your sessions. You can do so by editing the config/session.phpfile in your laravel configuration.

增加lifetime您的会话。您可以通过编辑config/session.phpLaravel 配置中的文件来实现。

/*
|--------------------------------------------------------------------------
| 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,

回答by user125661

I have a simple solution that:

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

  • Doesn't require you to extend the session lifetime.
  • Works with multiple tabs open.
  • Also works if the session did time out because the device was turned off.
  • 不需要您延长会话寿命。
  • 适用于打开多个选项卡。
  • 如果会话确实因为设备关闭而超时,也可以使用。

in /routes/web.php:

在 /routes/web.php 中:

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

This simply returns the current csrf token.

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

  • In case the token is invalid by the time this route gets called (for example when the device was turned off for a long time), it will return a new token, which was created by starting the session.
  • In case there still is a valid token, it will be returned. Since calling this route will extend the session, the token lifetime is extended as well.
  • 如果在调用此路由时令牌无效(例如,当设备长时间关闭时),它将返回一个新令牌,该令牌是通过启动会话创建的。
  • 如果仍然有一个有效的令牌,它将被返回。由于调用此路由会延长会话,因此令牌生命周期也会延长。

Because this only returns a new token when necessary, there are no problems when having multiple tabs open as described by @Adam.

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

You just need to make sure to call the above route every X minutes (where X is your session lifetime - 5 minutes), and update any _tokeninputs. I do this as follows (i use momentjs and axios here):

您只需要确保每 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();
        });
    });
}

回答by Jean Freitas

try this in your main layout file

在你的主布局文件中试试这个

@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

回答by Aref Ben Lazrek

You may try Caffeine for Laravel packageit set an interval then it refresh the token like suggested in some answers also it will be added automatically in every form having csrf token

您可以尝试为 Laravel 包尝试Caffeine,它设置一个间隔,然后按照某些答案中的建议刷新令牌,并且它将自动添加到具有 csrf 令牌的每种形式中