基于空闲用户的 Angularjs 自动注销

是否有可能确定用户是否处于非活动状态,并在使用 angularjs 10分钟的非活动状态后自动将其注销?

我试图避免使用 jQuery,但是我找不到任何关于如何在 angularjs 中实现这一点的教程或文章。如果你能帮忙,我将不胜感激。

108876 次浏览

应该有不同的方法来做到这一点,每种方法都应该比另一种方法更适合特定的应用程序。对于大多数应用程序,您只需处理键或鼠标事件,并适当地启用/禁用注销计时器。也就是说,在我的头顶上,一个“花哨的”AngularJS-y 解决方案正在监视摘要循环,如果在最后[指定的持续时间]没有触发,那么注销。就像这样。

app.run(function($rootScope) {
var lastDigestRun = new Date();
$rootScope.$watch(function detectIdle() {
var now = new Date();
if (now - lastDigestRun > 10*60*60) {
// logout here, like delete cookie, navigate to login ...
}
lastDigestRun = now;
});
});

我尝试了 Buu 的方法,但是由于触发消化器执行的事件数量太多,包括 $time 和 $timeout 函数的执行,因此无法完全正确地使用 Buu。这使得应用程序处于无论用户输入如何都不会空闲的状态。

如果您实际上需要跟踪用户的空闲时间,我不确定有一个好的角度方法。我想建议一个更好的方法是代表 Witoldz 在这里 https://github.com/witoldsz/angular-http-auth。此方法将提示用户在执行需要其凭据的操作时重新进行身份验证。在用户进行身份验证之后,重新处理以前失败的请求,应用程序继续运行,就像什么都没有发生一样。

这将处理您可能担心的问题,即在用户的会话处于活动状态时让它们过期,因为即使它们的身份验证过期,它们仍然能够保留应用程序状态,而不会丢失任何工作。

如果您的客户端上有某种会话(cookie、令牌等) ,您也可以观察它们,并在它们过期时触发您的注销过程。

app.run(['$interval', function($interval) {
$interval(function() {
if (/* session still exists */) {
} else {
// log out of client
}
}, 1000);
}]);

更新: 这里有一个关注点。 这表明,只有当时间间隔滴答作响时,才会更新摘要程序的运行时。一旦间隔达到它的最大计数,然后消化器将不再运行。

我编写了一个名为 Ng-Idle的模块,在这种情况下可能对您有用

基本上,它有一个服务,可以为您的空闲时间启动一个计时器,该计时器可以被用户活动(事件,例如单击、滚动、输入)中断。还可以通过调用服务上的方法手动中断超时。如果超时没有中断,那么它会倒计时一个警告,您可以通知用户他们将被注销。如果在警告倒计时达到0之后它们没有响应,则会广播一个应用程序可以响应的事件。在您的情况下,它可以发出一个请求来终止它们的会话并重定向到一个登录页面。

此外,它还有一个 keep-alive 服务,可以每隔一段时间 ping 一些 URL。这可以被你的应用程序用来保持用户的会话活跃,而他们是活跃的。默认情况下,空闲服务与 keep-alive 服务集成,如果空闲,则暂停 ping,并在返回时恢复它。

所有的信息,你需要开始是在 工地与更多的细节在 维基百科。但是,下面的配置片段显示了当它们超时时如何将它们注销。

angular.module('demo', ['ngIdle'])
// omitted for brevity
.config(function(IdleProvider, KeepaliveProvider) {
IdleProvider.idle(10*60); // 10 minutes idle
IdleProvider.timeout(30); // after 30 seconds idle, time the user out
KeepaliveProvider.interval(5*60); // 5 minute keep-alive ping
})
.run(function($rootScope) {
$rootScope.$on('IdleTimeout', function() {
// end their session and redirect to login
});
});

Ng-Idle 看起来是个不错的选择,但是我不能理解 Brian F 的修改,而且我也想超时到睡眠阶段,而且我脑子里有一个非常简单的用例。我把它缩减到下面的代码。它通过挂钩事件来重置超时标志(懒惰地放置在 $rootScope 中)。它只在用户返回(并触发事件)时检测超时发生,但这对我来说已经足够好了。我无法在这里获得棱角的 $location,但是使用 document.location.href 可以完成这项工作。

在. config 运行之后,我将这个命令插入到我的 app.js 中。

app.run(function($rootScope,$document)
{
var d = new Date();
var n = d.getTime();  //n in ms


$rootScope.idleEndTime = n+(20*60*1000); //set end time to 20 min from now
$document.find('body').on('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart', checkAndResetIdle); //monitor events


function checkAndResetIdle() //user did something
{
var d = new Date();
var n = d.getTime();  //n in ms


if (n>$rootScope.idleEndTime)
{
$document.find('body').off('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart'); //un-monitor events


//$location.search('IntendedURL',$location.absUrl()).path('/login'); //terminate by sending to login page
document.location.href = 'https://whatever.com/myapp/#/login';
alert('Session ended due to inactivity');
}
else
{
$rootScope.idleEndTime = n+(20*60*1000); //reset end time
}
}
});

使用 Boo 的方法,但是不喜欢用户只有在另一个摘要运行时才被踢出,这意味着用户在尝试在页面中做某些事情之前一直登录,然后立即被踢出。

我试图强制注销使用间隔检查,如果最后的行动时间是超过30分钟以前的每一分钟。我在 $routeChangeStart 上钩住了它,但也可以在 $rootScope 上钩住它。$看看阿布的例子。

app.run(function($rootScope, $location, $interval) {


var lastDigestRun = Date.now();
var idleCheck = $interval(function() {
var now = Date.now();
if (now - lastDigestRun > 30*60*1000) {
// logout
}
}, 60*1000);


$rootScope.$on('$routeChangeStart', function(evt) {
lastDigestRun = Date.now();
});
});

查看使用 angularjs演示并查看您的浏览器日志

<!DOCTYPE html>
<html ng-app="Application_TimeOut">
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script>
</head>


<body>
</body>


<script>


var app = angular.module('Application_TimeOut', []);
app.run(function($rootScope, $timeout, $document) {
console.log('starting run');


// Timeout timer value
var TimeOutTimerValue = 5000;


// Start a timeout
var TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
var bodyElement = angular.element($document);


/// Keyboard Events
bodyElement.bind('keydown', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('keyup', function (e) { TimeOut_Resetter(e) });


/// Mouse Events
bodyElement.bind('click', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousemove', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('DOMMouseScroll', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousewheel', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('mousedown', function (e) { TimeOut_Resetter(e) });


/// Touch Events
bodyElement.bind('touchstart', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('touchmove', function (e) { TimeOut_Resetter(e) });


/// Common Events
bodyElement.bind('scroll', function (e) { TimeOut_Resetter(e) });
bodyElement.bind('focus', function (e) { TimeOut_Resetter(e) });


function LogoutByTimer()
{
console.log('Logout');


///////////////////////////////////////////////////
/// redirect to another page(eg. Login.html) here
///////////////////////////////////////////////////
}


function TimeOut_Resetter(e)
{
console.log('' + e);


/// Stop the pending timeout
$timeout.cancel(TimeOut_Thread);


/// Reset the timeout
TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
}


})
</script>


</html>

下面的代码是纯 javascript 版本

<html>
<head>
<script type="text/javascript">
function logout(){
console.log('Logout');
}


function onInactive(millisecond, callback){
var wait = setTimeout(callback, millisecond);
document.onmousemove =
document.mousedown =
document.mouseup =
document.onkeydown =
document.onkeyup =
document.focus = function(){
clearTimeout(wait);
wait = setTimeout(callback, millisecond);
};
}
</script>
</head>
<body onload="onInactive(5000, logout);"></body>
</html>

更新

我把我的解决方案更新为“汤姆建议”。

<!DOCTYPE html>
<html ng-app="Application_TimeOut">
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script>
</head>


<body>
</body>


<script>
var app = angular.module('Application_TimeOut', []);
app.run(function($rootScope, $timeout, $document) {
console.log('starting run');


// Timeout timer value
var TimeOutTimerValue = 5000;


// Start a timeout
var TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
var bodyElement = angular.element($document);


angular.forEach(['keydown', 'keyup', 'click', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mousedown', 'touchstart', 'touchmove', 'scroll', 'focus'],
function(EventName) {
bodyElement.bind(EventName, function (e) { TimeOut_Resetter(e) });
});


function LogoutByTimer(){
console.log('Logout');
///////////////////////////////////////////////////
/// redirect to another page(eg. Login.html) here
///////////////////////////////////////////////////
}


function TimeOut_Resetter(e){
console.log(' ' + e);


/// Stop the pending timeout
$timeout.cancel(TimeOut_Thread);


/// Reset the timeout
TimeOut_Thread = $timeout(function(){ LogoutByTimer() } , TimeOutTimerValue);
}


})
</script>
</html>

点击这里查看 Plunker 更新版本

我觉得 Buu 的消化循环手表是个天才。谢谢分享。正如其他人所指出的,$time 也会导致摘要循环运行。为了自动将用户注销,我们可以使用 setInterval,它不会导致摘要循环。

app.run(function($rootScope) {
var lastDigestRun = new Date();
setInterval(function () {
var now = Date.now();
if (now - lastDigestRun > 10 * 60 * 1000) {
//logout
}
}, 60 * 1000);


$rootScope.$watch(function() {
lastDigestRun = new Date();
});
});

你也可以用一种比注入多个提供者更直接的方式来完成使用 angular-activity-monitor,它使用 setInterval()(相对于棱角的 $interval)来避免手动触发摘要循环(这对于防止无意中保持条目存活很重要)。

最终,您只需订阅几个事件,这些事件确定用户何时处于非活动状态或变得非常接近。因此,如果您想在10分钟的不活动后注销用户,您可以使用以下代码片段:

angular.module('myModule', ['ActivityMonitor']);


MyController.$inject = ['ActivityMonitor'];
function MyController(ActivityMonitor) {
// how long (in seconds) until user is considered inactive
ActivityMonitor.options.inactive = 600;


ActivityMonitor.on('inactive', function() {
// user is considered inactive, logout etc.
});


ActivityMonitor.on('keepAlive', function() {
// items to keep alive in the background while user is active
});


ActivityMonitor.on('warning', function() {
// alert user when they're nearing inactivity
});
}

我为此使用了 n- 空闲,并添加了一点注销和令牌空代码,它工作正常,您可以尝试这一点。 感谢@HackedByChinese 制作了这么好的模块。

闲置时间中,我刚刚删除了我的会话数据和令牌。

这是我的密码

$scope.$on('IdleTimeout', function () {
closeModals();
delete $window.sessionStorage.token;
$state.go("login");
$scope.timedout = $uibModal.open({
templateUrl: 'timedout-dialog.html',
windowClass: 'modal-danger'
});
});

我想把这个问题的答案扩展到任何可能在更大的项目中使用它的人,你可能会意外地附加多个事件处理程序,这个程序会表现得很奇怪。

为了解决这个问题,我使用了一个由工厂公开的单例函数,从这个单例函数可以调用角度应用程序中的 inactivityTimeoutFactory.switchTimeoutOn()inactivityTimeoutFactory.switchTimeoutOff(),以分别激活和停用由于不活动功能而导致的注销。

这样,无论尝试激活超时过程多少次,都可以确保只运行事件处理程序的单个实例,从而使用户可能从不同路由登录的应用程序更容易使用。

这是我的代码:

'use strict';


angular.module('YOURMODULENAME')
.factory('inactivityTimeoutFactory', inactivityTimeoutFactory);


inactivityTimeoutFactory.$inject = ['$document', '$timeout', '$state'];


function inactivityTimeoutFactory($document, $timeout, $state)  {
function InactivityTimeout () {
// singleton
if (InactivityTimeout.prototype._singletonInstance) {
return InactivityTimeout.prototype._singletonInstance;
}
InactivityTimeout.prototype._singletonInstance = this;


// Timeout timer value
const timeToLogoutMs = 15*1000*60; //15 minutes
const timeToWarnMs = 13*1000*60; //13 minutes


// variables
let warningTimer;
let timeoutTimer;
let isRunning;


function switchOn () {
if (!isRunning) {
switchEventHandlers("on");
startTimeout();
isRunning = true;
}
}


function switchOff()  {
switchEventHandlers("off");
cancelTimersAndCloseMessages();
isRunning = false;
}


function resetTimeout() {
cancelTimersAndCloseMessages();
// reset timeout threads
startTimeout();
}


function cancelTimersAndCloseMessages () {
// stop any pending timeout
$timeout.cancel(timeoutTimer);
$timeout.cancel(warningTimer);
// remember to close any messages
}


function startTimeout () {
warningTimer = $timeout(processWarning, timeToWarnMs);
timeoutTimer = $timeout(processLogout, timeToLogoutMs);
}


function processWarning() {
// show warning using popup modules, toasters etc...
}


function processLogout() {
// go to logout page. The state might differ from project to project
$state.go('authentication.logout');
}


function switchEventHandlers(toNewStatus) {
const body = angular.element($document);
const trackedEventsList = [
'keydown',
'keyup',
'click',
'mousemove',
'DOMMouseScroll',
'mousewheel',
'mousedown',
'touchstart',
'touchmove',
'scroll',
'focus'
];


trackedEventsList.forEach((eventName) => {
if (toNewStatus === 'off') {
body.off(eventName, resetTimeout);
} else if (toNewStatus === 'on') {
body.on(eventName, resetTimeout);
}
});
}


// expose switch methods
this.switchOff = switchOff;
this.switchOn = switchOn;
}


return {
switchTimeoutOn () {
(new InactivityTimeout()).switchOn();
},
switchTimeoutOff () {
(new InactivityTimeout()).switchOff();
}
};


}

[在应用程序引用 js 文件中添加以下脚本][1] Https://rawgit.com/hackedbychinese/ng-idle/master/angular-idle.js

var mainApp = angular.module('mainApp', ['ngIdle']);
mainApp.config(function (IdleProvider, KeepaliveProvider) {
IdleProvider.idle(10*60); // 10 minutes idel user
IdleProvider.timeout(5);
KeepaliveProvider.interval(10);
});


mainApp
.controller('mainController', ['$scope', 'Idle', 'Keepalive', function ($scope,
Idle, Keepalive) {
//when login then call below function
Idle.watch();
$scope.$on('IdleTimeout', function () {
$scope.LogOut();
//Logout function or redirect to logout url
});
});