【问题标题】:Auto logout with Angularjs based on idle user基于空闲用户使用Angularjs自动注销
【发布时间】:2013-10-10 16:34:51
【问题描述】:

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

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

【问题讨论】:

标签: angularjs ng-idle


【解决方案1】:

我编写了一个名为Ng-Idle 的模块,在这种情况下可能对您有用。 Here is the page which contains instructions and a demo.

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

此外,它还有一个保持活动状态的服务,可以每隔一段时间 ping 一些 URL。您的应用可以使用此功能在用户处于活动状态时保持用户会话处于活动状态。 idle 服务默认与 keep-alive 服务集成,空闲时暂停 ping,返回时恢复。

您需要开始的所有信息都在site 上,更多详细信息在wiki 上。但是,这里有一个配置的 sn-p 显示如何在它们超时时将它们注销。

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

【讨论】:

  • 嘿,你的方法对我来说非常有效,直到我在移动 safari 上遇到问题,当页面进入后台时超时会暂停。我不得不修改它以在每个手表上设置一个空闲时间戳,然后在中断时更新。在每次更新中断之前,虽然我检查了 idleTimeout 没有过期,这可以解决 Safari 问题(不自动注销,但在第一次触摸/鼠标/单击时注销)。
  • @BrianF 很有趣。如果您愿意并且有能力,我会对您的更改的拉取请求感兴趣,或者至少有一个带有您更改的代码示例的问题。
  • 看看我在 github 上发布的一个示例 - github.com/brianfoody/Angular/blob/master/src/idle.js。我不使用倒计时或 keepAlive 功能,所以这只是一个精简版本,但您应该能够使用 idleCutOffMoment 看到我的修复
  • @BrianF 谢谢。我知道您使用的是自定义版本,所以这可能无关紧要,但我会将此添加到正式版本中。
  • @HackedByChinese 谢谢 :) 在你的 sn-p 中,你需要做的就是让它工作吗?在我在.run() 函数中添加Idle.watch() 之前它对我不起作用,直到我这样做之前IdleTimeout 事件根本没有触发。我在你的 github 演示上看到了对 Idle.watch() 的调用,所以我就是从那里得到它的。
【解决方案2】:

查看使用angularjsDemo 并查看您的浏览器日志

<!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>

Click here to see at Plunker for updated version

【讨论】:

  • 一个很酷的解决方案,可以通过循环事件名称来缩短:var eventNames = ['keydown', 'keyup', 'click', 'mousemove', 'DOMMouseScroll', 'mousewheel' , 'mousedown', 'touchstart', 'touchmove', 'scroll', 'focus']; for (var i = 0; i
  • @Tom 谢谢你的建议,我根据你的建议更新了我的答案。
  • 我知道这是一个旧答案,但对于任何阅读本文的人,您应该在注销部分关闭事件。现在(2017 年 12 月)绑定已弃用,因此您应该执行 bodyElement.on(...) 并在 LogoutByTimer 内部执行 bodyElement.off(...)
  • 这很酷。我添加了 $uib.modal 以向用户发出 onlick 警告。
  • 根据我的项目细节稍微修改了您的解决方案,但它有效!谢谢!
【解决方案3】:

应该有不同的方法来做到这一点,并且每种方法都应该比另一种更适合特定的应用程序。对于大多数应用程序,您可以简单地处理键或鼠标事件并适当地启用/禁用注销计时器。也就是说,在我的脑海中,一个“花哨的”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;
  });
});

【讨论】:

  • 我认为这是一个非常新颖的解决方案,并且玩了很多次。我遇到的主要问题是,这将在许多其他可能不是用户驱动的事件($intervals、$timeouts 等)上运行,并且这些事件将重置您的 lastDigestRun。
  • 您可以使用这种方法,而不是像下面的 ngIdle 模块那样检查每个摘要,而是检查某些事件。即 $document.find('body').on('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart', checkIdleTimeout);
  • 我喜欢摘要手表的想法,但如果你有网络套接字或正在运行的东西,你可以随时使用它;暗示失败。
  • @BrianF 嗯,webSockets 会像 Brian 所说的那样触发诸如“mousemove”、“keydown”、“DOM-xxx”之类的事件吗?我想,通常情况下不会是这样,不是吗?
  • "if (now - lastDigestRun > 10*60*60) {" 是不是要等1分钟?
【解决方案4】:

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

【讨论】:

  • 很好的例子。在第 3 行“var lastRun = Date.now();”我相信你的意思是变量是“lastDigestRun”
  • 为了实验,我把它改成了一分钟不活动后超时。当用户处于活动状态时,它会一直超时。什么给了?
  • 在这种情况下使用$rootScope.$watch 需要切换到setInterval,因为$interval 将触发每个函数调用的摘要,从而有效地重置您的lastDigestRun
  • @v-tec 我在我的应用程序中使用了你的方法,但是我怎样才能通过使用 clearInterval 清除我无法停止的间隔,这是演示:github.com/MohamedSahir/UserSession
【解决方案5】:

您还可以使用angular-activity-monitor 以比注入多个提供程序更直接的方式完成它,它使用setInterval()(相对于 Angular 的$interval)来避免手动触发摘要循环(这对于防止保留项目很重要无意中活着)。

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

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:Web 应用程序中的脚本不会跳出它所在的窗口(选项卡)。所以不,它不会跟踪其他活动
【解决方案6】:

我尝试了 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。 这表明消化器运行时间仅在间隔滴答时更新。一旦间隔达到最大计数,消化器将不再运行。

【讨论】:

    【解决方案7】:

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

    【讨论】:

      【解决方案8】:

      我认为 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" 是毫秒数吗?
      • 在性能视图中哪种方法效果更好?看最后的摘要还是看事件???
      【解决方案9】:

      我为此使用了 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'
              });
          });
      

      【讨论】:

        【解决方案10】:

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

        为了摆脱这种情况,我使用了一个工厂公开的单例函数,您可以在 Angular 应用程序中调用 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();
            }
          };
        
        }
        

        【讨论】:

          【解决方案11】:

          [在应用程序引用 js 文件中添加以下脚本][1] [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
                });
            });
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-10-14
            • 2013-01-12
            • 2019-03-17
            • 2019-07-02
            • 2014-10-20
            • 1970-01-01
            • 1970-01-01
            • 2019-12-27
            相关资源
            最近更新 更多