基于闲置用户使用Angularjs自动注销


78

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

我试图避免使用jQuery,但是在angularjs中找不到任何有关如何执行此操作的教程或文章。任何帮助,将不胜感激。



6
@Stewie他写道,他试图避免使用jQuery ...
Sebastian

您有此功能的任何选择吗?
HP 411

Answers:


112

我编写了一个名为的模块Ng-Idle,在这种情况下可能对您有用。这是包含说明和演示的页面。

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

此外,它还具有一个保持活动的服务,该服务可以间隔ping一些URL。您的应用可以使用它来使用户的会话保持活动状态。默认情况下,空闲服务与kee​​p-alive服务集成在一起,如果它们变为空闲,则挂起ping,并在它们返回时恢复。

您需要入门的所有信息都在该站点上,而Wiki中有更多详细信息。但是,这是一个配置片段,显示了如何在超时时将其注销。

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

2
嘿,您的方法对我来说是完美的,直到我在移动浏览器上遇到问题时,页面进入后台后暂停超时。我必须对其进行修改以在每只手表上设置一个空闲时间戳,然后在中断时进行更新。在每次中断更新之前,我都会检查idleTimeout尚未过期,这可以解决Safari问题(不会自动注销,而是在第一次触摸/鼠标/单击时注销)。
Brian F

@BrianF有趣。如果您愿意且有能力,那么我将对您的更改提出请求,或者至少要解决一个带有更改示例代码的问题。
moribvndvs 2014年

看看我在github-github.com/brianfoody/Angular/blob/master/src/idle.js上发布的示例。我不使用countdown或keepAlive功能,所以这只是精简版,但是您应该可以使用idleCutOffMoment查看我的修复程序
Brian F

3
@BrianF谢谢你。我知道您使用的是自定义版本,因此可能没关系,但是我会将其添加到正式版本中。
moribvndvs 2014年

1
@HackedByChinese谢谢:)在您的摘要中,这是您需要做的所有事情才能使其正常工作吗?它并没有为我工作,直到我加入Idle.watch().run()功能,IdleTimeout直到我做了该事件并没有在所有射击。我Idle.watch()在您的github演示上看到了对的调用,所以这就是我从那里得到的。
鲍里斯(Boris)

22

查看正在使用的演示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>

更新

我将解决方案更新为@Tom建议。

<!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的更新版本


2
一个不错的解决方案可以通过循环事件名称来缩短:var eventNames = ['keydown','keyup','click','mousemove','DOMMouseScroll','mousewheel','mousedown','touchstart' ,“触摸移动”,“滚动”,“焦点”];for(var i = 0; i <eventNames.length; i ++){bodyElement.bind(eventNames [i],TimeOut_Resetter); }
汤姆(Tom)

@Tom谢谢您的建议,我更新了我的回答作为您的建议。
弗兰克·迈亚特

我知道这是一个古老的答案,但是对于任何阅读此书的人,您都应该在注销部分中关闭事件。现在(2017年12月)不推荐使用bind,因此您应该执行bodyElement.on(...)和内部LogoutByTimer执行bodyElement.off(...)
GChamon

这很酷。我添加了$ uib.modal给用户警告onlick。
Fergus

略微修改了您针对我的项目细节的解决方案,但它可以正常工作!谢谢!
mp_loki

19

应该有不同的方法来执行此操作,并且每种方法都应该比其他应用程序更适合特定的应用程序。对于大多数应用程序,您只需处理键或鼠标事件并适当地启用/禁用注销计时器即可。就是说,在我的头上,一个“花哨”的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;
  });
});

4
我认为这是一个非常新颖的解决方案,并且已经进行了很多尝试。我的主要问题是,它将在许多其他非用户驱动的事件($ interval,$ timeouts等)上运行,并且这些事件将重置您的lastDigestRun。
Seth M.

1
您可以使用此方法,而不是在每个摘要中都可以像ngIdle模块一样在下面检查某些事件。即$ document.find('body')。on('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart',checkIdleTimeout);
Brian F

我喜欢摘要监视的想法,但是如果您有Web套接字或正在运行的东西,则可以始终使用它。暗示打败了这一点。
amcdnl 2014年

@BrianF Mmh会像Brian所说的那样,webSockets触发诸如“ mousemove”,“ keydown”,“ DOM-xxx”之类的事件吗?我想通常情况并非如此,不是吗?
freeeman

3
“如果(现在-lastDigestRun> 10 * 60 * 60){”是否意味着我必须等待1分钟?
Tomasz Waszczyk

11

使用Boo的方法,但是不喜欢这样的事实,即用户仅在运行另一个摘要时才被踢开,这意味着用户一直保持登录状态,直到他尝试在页面中执行某项操作,然后立即开始。

我正在尝试使用间隔强制注销,该间隔每分钟检查一次上一次操作时间是否超过30分钟。我将其钩在$ routeChangeStart上,但也可以钩在$ rootScope。$ watch上,如Boo的示例所示。

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

1
很好的例子。在第3行上,“ var lastRun = Date.now();” 我相信您的意思是变量为“ lastDigestRun”
马丁

为了实验起见,我将其更改为在闲置一分钟后超时。并且当用户处于活动中间时,它会不断超时。是什么赋予了?
BHR

$rootScope.$watch在这种情况下使用,需要切换到setInterval,因为这$interval将触发每个函数调用的摘要,从而有效地重置lastDigestRun
alalonde '16

@ v-tec我在您的应用程序中使用了您的方法,但是如何使用clearInterval清除无法停止的间隔,这是演示:github.com/MohamedSahir/UserSession
Mohamed Sahir

6

您也可以angular-activity-monitor比注入多个提供程序更直接的方式完成使用,并且它使用setInterval()(相对于angular的$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
  });
}

请不要针对多个问题发布相同的答案。发布一个好的答案,然后投票/标记以将其他问题重复作为一个重复。如果问题不是重复的,请定制对问题的答案
马丁·彼得斯

我正在实施此解决方案,非常酷,但我对此表示怀疑,它是否也跟踪其他浏览器活动?我只想限制它,我的应用程序意味着用户在我的应用程序中处于空闲状态,然后只能自动注销
user1532976

@ user1532976:Web应用程序中的脚本不会脱离它所在的窗口(选项卡)。所以不,它不会跟踪其他活动
DdW

3

我尝试了Buu的方法,但由于触发摘要程序执行的事件数量庞大,包括$ interval和$ timeout函数正在执行,因此无法完全正确。这使应用程序处于无论用户输入如何都永远不会空闲的状态。

如果您确实需要跟踪用户的空闲时间,则不确定是否有一个好的角度方法。我建议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);
}]);

更新:这是一个简单的例子,说明了这一问题。http://plnkr.co/edit/ELotD8W8VAeQfbYFin1W。这说明摘要器的运行时间仅在间隔增加时才更新。一旦间隔达到最大计数,摘要器将不再运行。


3

ng-Idle看起来很可行,但是我无法弄清Brian F的修改,也想让睡眠会话超时,因此我想到了一个非常简单的用例。我将其简化为以下代码。它钩住事件以重置超时标志(延迟放置在$ rootScope中)。它仅检测到用户返回(并触发事件)时发生了超时,但这对我来说已经足够了。我无法使angular的$ 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
        }
    }
});

1

我认为Buu的摘要循环手表真是天才。感谢分享。正如其他人指出的那样,$ interval也会导致摘要循环运行。为了自动注销用户,我们可以使用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();
    });
});

“ 10 * 60 * 1000”是毫秒数吗?
Tomasz Waszczyk

在性能视图中,哪种方法会更好?观看最后摘要或观看事件???
Ravi Shanker Reddy '18

1

我已经为此使用ng-idle并添加了一些注销和令牌空代码,并且它工作正常,您可以尝试一下。感谢@HackedByChinese制作了如此出色的模块。

IdleTimeout中,我刚刚删除了会话数据和令牌。

这是我的代码

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

1

我想将答案扩展到可能在更大的项目中使用它的任何人,您可能会不小心附加多个事件处理程序,并且该程序的行为会很奇怪。

为了消除这种情况,我使用了工厂公开的单例函数,由于不活动的功能,您将在其中调用Angle应用程序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();
    }
  };

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.