Cierre de sesión automático con Angularjs según el usuario inactivo

78

¿Es posible determinar si un usuario está inactivo y cerrar sesión automáticamente después de, digamos, 10 minutos de inactividad usando angularjs?

Estaba tratando de evitar el uso de jQuery, pero no puedo encontrar tutoriales o artículos sobre cómo hacer esto en angularjs. Cualquier ayuda sería apreciada.

usuario2101411
fuente
6
@Stewie escribió que intenta evitar jQuery ...
Sebastian
¿Tiene alguna opción para esta funcionalidad?
HP's 411

Respuestas:

112

Escribí un módulo llamado Ng-Idleque puede serle útil en esta situación. Aquí está la página que contiene instrucciones y una demostración.

Básicamente, tiene un servicio que inicia un temporizador durante el tiempo de inactividad que puede verse interrumpido por la actividad del usuario (eventos, como hacer clic, desplazarse, escribir). También puede interrumpir manualmente el tiempo de espera llamando a un método en el servicio. Si el tiempo de espera no se interrumpe, se muestra una advertencia en la que puede alertar al usuario de que se cerrará la sesión. Si no responden después de que la cuenta regresiva de advertencia llega a 0, se transmite un evento al que su aplicación puede responder. En su caso, podría emitir una solicitud para cerrar su sesión y redirigirla a una página de inicio de sesión.

Además, tiene un servicio de mantenimiento que puede hacer ping a alguna URL en un intervalo. Esto puede ser utilizado por su aplicación para mantener viva la sesión de un usuario mientras están activos. El servicio inactivo se integra de forma predeterminada con el servicio Keep-Alive, suspendiendo el ping si quedan inactivos y reanudándolo cuando regresan.

Toda la información que necesita para comenzar está en el sitio con más detalles en la wiki . Sin embargo, aquí hay un fragmento de configuración que muestra cómo cerrar la sesión cuando se agote el tiempo de espera.

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
    });
});
moribvndvs
fuente
2
Oye, tu enfoque me funcionó perfectamente hasta que tuve problemas en el safari móvil donde los tiempos de espera se pausan cuando la página pasa a segundo plano. Tuve que modificarlo para establecer una marca de tiempo inactiva en cada reloj que luego se actualiza en caso de interrupción. Sin embargo, antes de cada actualización en la interrupción, verifico que el idleTimeout no haya expirado, lo que soluciona el problema de Safari (no se cierra automáticamente, pero se cierra con el primer toque / mouse / clic).
Brian F
@BrianF Interesante. Si está dispuesto y puede, me interesaría una solicitud de extracción con sus cambios, o al menos un problema abierto con un ejemplo del código con sus cambios.
moribvndvs
eche un vistazo a una muestra que vomité en github: github.com/brianfoody/Angular/blob/master/src/idle.js . No uso la funcionalidad de cuenta regresiva o keepAlive, por lo que esta fue solo una versión simplificada
Brian F
3
@BrianF Gracias por eso. Me doy cuenta de que usa una versión personalizada, por lo que puede que no importe, pero agregaré esto a un lanzamiento oficial.
moribvndvs
1
@HackedByChinese Gracias :) en su fragmento, ¿esto es todo lo que necesita hacer para que funcione? No funcionó para mí hasta que agregué Idle.watch()la .run()función, el IdleTimeoutevento no se activó en absoluto hasta que hice eso. Idle.watch()Sin embargo, vi la llamada a en tu demo de github, así que de ahí la saqué.
Boris
24

Vea la demostración que está usando angularjsy vea el registro de su navegador

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

El siguiente código es la versión pura de 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>

ACTUALIZAR

Actualicé mi solución como sugerencia de @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>

Haga clic aquí para ver en Plunker para obtener una versión actualizada

Frank Myat Jue
fuente
2
Una solución interesante, podría ser más corta haciendo un bucle sobre los nombres de los eventos: var eventNames = ['keydown', 'keyup', 'click', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mousedown', 'touchstart' , 'touchmove', 'scroll', 'focus']; for (var i = 0; i <eventNames.length; i ++) {bodyElement.bind (eventNames [i], TimeOut_Resetter); }
Tom
@Tom gracias por su sugerencia, actualicé mi respuesta como su sugerencia.
Frank Myat jueves
Sé que esta es una respuesta antigua, pero para cualquiera que lea esto, debería desactivar los eventos en la sección de cierre de sesión. En este momento (diciembre de 2017) el enlace está en desuso, por lo que debe ejecutarlo bodyElement.on(...)y LogoutByTimerejecutarlo desde adentrobodyElement.off(...)
GChamon
Esto está muy bien. Agregué $ uib.modal para advertir al usuario de onlick.
Fergus
modificó ligeramente su solución a los detalles de mi proyecto, ¡pero funciona! ¡Gracias!
mp_loki
19

Debe haber diferentes formas de hacerlo y cada enfoque debe adaptarse mejor a una aplicación particular que a otra. Para la mayoría de las aplicaciones, simplemente puede manejar eventos de teclas o mouse y habilitar / deshabilitar un temporizador de cierre de sesión de manera apropiada. Dicho esto, en la parte superior de mi cabeza, una solución "elegante" de AngularJS-y está monitoreando el bucle de resumen, si no se ha activado ninguno durante la última [duración especificada], cierre la sesión. Algo como esto.

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 Nguyen
fuente
4
Creo que esta es una solución bastante novedosa y jugué bastante con ella. El principal problema que tengo es que esto se ejecutará en muchos otros eventos que posiblemente no sean controlados por el usuario ($ intervalos, $ tiempos de espera, etc.) y estos eventos restablecerán su lastDigestRun.
Seth M.
1
Puede usar este método y, en lugar de en cada resumen, simplemente verifique ciertos eventos como lo hace el módulo ngIdle a continuación. es decir, $ document.find ('cuerpo'). on ('mousemove keydown DOMMouseScroll mousewheel mousedown touchstart', checkIdleTimeout);
Brian F
Me gusta la idea del reloj de resumen, pero si tiene enchufes web o algo en funcionamiento, siempre puede estar activo con eso; insinuaciones que derrotan el punto.
amcdnl
@BrianF Mmh, ¿webSockets activa eventos como "mousemove", "keydown", "DOM-xxx" como los que dice Brian? Supongo que, por lo general, este no sería el caso, ¿verdad?
freeeman
3
"if (now - lastDigestRun> 10 * 60 * 60) {" ¿significa que tengo que esperar 1 minuto?
Tomasz Waszczyk
11

Jugado con el enfoque de Boo, sin embargo, no me gusta el hecho de que el usuario fue expulsado solo una vez que se ejecuta otro resumen, lo que significa que el usuario permanece conectado hasta que intenta hacer algo dentro de la página y luego se inicia inmediatamente.

Estoy tratando de forzar el cierre de sesión usando un intervalo que verifica cada minuto si la última acción fue hace más de 30 minutos. Lo enganché en $ routeChangeStart, pero también podría engancharlo en $ rootScope. $ Watch como en el ejemplo de 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();  
    });
});
v-tec
fuente
1
Buen ejemplo. En la línea 3 "var lastRun = Date.now ();" Creo que pretendías que la variable fuera "lastDigestRun"
Martin
Por el bien del experimento, lo cambié a tiempo de espera después de un minuto de inactividad. Y sigue agotando el tiempo mientras el usuario está en medio de una actividad. ¿lo que da?
BHR
Usar $rootScope.$watchen este caso requeriría cambiar a setInterval, ya $intervalque activará un resumen en cada llamada de función, restableciendo efectivamente su archivo lastDigestRun.
alalonde
@ v-tec Estoy usando su enfoque en mi aplicación, pero ¿cómo podría borrar el intervalo que no puedo detener usando clearInterval? Esta es la demostración: github.com/MohamedSahir/UserSession
Mohamed Sahir
6

También puede lograr el uso angular-activity-monitorde una manera más sencilla que inyectar múltiples proveedores y usa setInterval()(en lugar de angular $interval) para evitar activar manualmente un bucle de resumen (que es importante para evitar mantener los elementos vivos de forma involuntaria).

En última instancia, solo se suscribe a algunos eventos que determinan cuándo un usuario está inactivo o se acerca. Entonces, si desea cerrar la sesión de un usuario después de 10 minutos de inactividad, puede usar el siguiente fragmento:

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
  });
}
Sean3z
fuente
No publique respuestas idénticas a varias preguntas. Publique una buena respuesta, luego vote / marque para cerrar las otras preguntas como duplicadas. Si la pregunta no es un duplicado, adapte sus respuestas a la pregunta .
Martijn Pieters
Estoy implementando esta solución, bastante bueno, pero tengo dudas, ¿está rastreando también otras actividades del navegador? Solo quiero restringir mi aplicación, lo que significa que el usuario está inactivo en mi aplicación y luego solo cierre de sesión automático
user1532976
@ user1532976: los scripts en una aplicación web no saldrán de la ventana (pestaña) en la que se encuentra. Así que no, no rastreará otras actividades
DdW
3

Probé el enfoque de Buu y no pude hacerlo bien debido a la gran cantidad de eventos que activan la ejecución del digestor, incluida la ejecución de las funciones $ interval y $ timeout. Esto deja la aplicación en un estado en el que nunca estará inactiva, independientemente de la entrada del usuario.

Si realmente necesita realizar un seguimiento del tiempo de inactividad del usuario, no estoy seguro de que haya un buen enfoque angular. Sugeriría que Witoldz represente un mejor enfoque aquí https://github.com/witoldsz/angular-http-auth . Este enfoque solicitará al usuario que se vuelva a autenticar cuando se realice una acción que requiera sus credenciales. Una vez que el usuario se ha autenticado, la solicitud fallida anterior se vuelve a procesar y la aplicación continúa como si nada.

Esto maneja la preocupación que podría tener de dejar que la sesión del usuario caduque mientras está activa, ya que incluso si su autenticación caduca, aún pueden retener el estado de la aplicación y no perder ningún trabajo.

Si tiene algún tipo de sesión en su cliente (cookies, tokens, etc.) también puede verlos y activar su proceso de cierre de sesión si caducan.

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

ACTUALIZACIÓN: Aquí hay un plunk que demuestra la preocupación. http://plnkr.co/edit/ELotD8W8VAeQfbYFin1W . Lo que esto demuestra es que el tiempo de ejecución del digestor se actualiza solo cuando el intervalo marca. Una vez que el intervalo alcanza el recuento máximo, el digestor dejará de funcionar.

Seth M.
fuente
3

ng-Idle parece el camino a seguir, pero no pude entender las modificaciones de Brian F y también quería tiempo de espera para una sesión de suspensión, también tenía un caso de uso bastante simple en mente. Lo reduje al código de abajo. Engancha eventos para restablecer un indicador de tiempo de espera (colocado perezosamente en $ rootScope). Solo detecta que se ha agotado el tiempo de espera cuando el usuario regresa (y activa un evento), pero eso es lo suficientemente bueno para mí. No pude hacer que la ubicación $ de angular funcione aquí, pero nuevamente, usando document.location.href hace el trabajo.

Pegué esto en mi app.js después de que se haya ejecutado .config.

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
        }
    }
});
James Bell
fuente
1

Creo que el ciclo de digestión de Buu es genial. Gracias por compartir. Como otros han señalado, $ interval también hace que se ejecute el ciclo de resumen. Podríamos, con el propósito de cerrar automáticamente la sesión del usuario, usar setInterval que no causará un bucle de resumen.

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();
    });
});
Declan McLaughlin
fuente
"10 * 60 * 1000" ¿es un número de milisegundos?
Tomasz Waszczyk
En la vista de rendimiento, ¿qué método funcionará mejor? Ver el último resumen o ver eventos ???
Ravi Shanker Reddy
1

He usado ng-idle para esto y agregué un pequeño código de cierre de sesión y token nulo y está funcionando bien, puedes probar esto. Gracias @HackedByChinese por hacer un módulo tan agradable.

En IdleTimeout, acabo de eliminar los datos de mi sesión y el token.

Aqui esta mi codigo

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

Me gustaría expandir las respuestas a quien pueda estar usando esto en un proyecto más grande, podría adjuntar accidentalmente múltiples controladores de eventos y el programa se comportaría de manera extraña.

Para deshacerme de eso, utilicé una función singleton expuesta por una fábrica, desde la cual llamarías inactivityTimeoutFactory.switchTimeoutOn()y inactivityTimeoutFactory.switchTimeoutOff()en tu aplicación angular para activar y desactivar respectivamente el cierre de sesión debido a la funcionalidad de inactividad.

De esta manera, se asegura de que solo está ejecutando una única instancia de los controladores de eventos, sin importar cuántas veces intente activar el procedimiento de tiempo de espera, lo que facilita su uso en aplicaciones donde el usuario puede iniciar sesión desde diferentes rutas.

Aquí está mi código:

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

}
GChamon
fuente