在laravel 5中处理过期令牌的最佳方法是什么。
我的意思是我有一个页面,它有一些执行ajax请求的链接。当页面加载时它们工作正常,但是当我等待一段时间后,我得到一个TOKEN MISMATCH错误。
现在,我必须刷新页面才能让它再次运行。但是,我不想刷新页面。我想要一些方法来刷新令牌或其他一些工作来解决它。
我希望你明白我的观点。
答案 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)
<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,
为什么我不喜欢上述任何答案:
使会话永久存在,并在固定时间后重新创建新的CSRF token
。如果用户打开多个水龙头,则会出现问题。每次轻按一次刷新CSRF
令牌,其他所有选项卡均无效。
此答案更好,因为它不会更改CSRF token
,因此不会影响多个选项卡。通过使用setInterval
在固定时间后进行js调用,可以使会话保持活跃状态。但是,setInterval
在PC进入睡眠状态时不起作用。因此,当PC进入睡眠状态时,会话可能会过期,这也是一种很可能的情况。因此,我不会尝试通过js调用使会话保持活动状态,而是将生存期延长至50年。
在会话超时时显示错误是可以的,但是最好是永远不要发生此问题。将生存期设置为6h是不够的,因为它很可能会打开一个选项卡几天。
所有其他答案都建议对有问题的路由禁用CSRF,但这当然是不可行的,因为它会带来很大的安全风险。