【问题标题】:How to give session idle timeout in angular 6?如何在角度 6 中给出会话空闲超时?
【发布时间】:2019-02-28 12:06:30
【问题描述】:

我们正在维护基于用户角色的会话。我们希望在会话空闲 5 分钟时实现超时功能。我们正在使用@ng-idle/core npm 模块来做到这一点。

我的服务文件:

 import { ActivatedRouteSnapshot } from '@angular/router';
 import { RouterStateSnapshot } from '@angular/router';
 import {Idle, DEFAULT_INTERRUPTSOURCES, EventTargetInterruptSource} from 
 '@ng-idle/core';
 @Injectable()
export class LoginActService implements CanActivate {
constructor(private authService: APILogService, private router: 
 Router,private idle: Idle) {
  idle.setIdle(10);
  idle.setTimeout(10);
 }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
 ): Observable<boolean>|Promise<boolean>|boolean {
let role = localStorage.getItem('currentUser');

if (localStorage.getItem('currentUser')) {
  if(next.data[0] == role){
   },600000) 
    return true;
  } 
}
else{
  this.router.navigate(['/'], { queryParams: { returnUrl: state.url }});
  return false;
  }
 }
}

对于示例,我使用 setIdle 超时 5 秒,但它没有发生。有人可以指导我如何做到这一点吗?

【问题讨论】:

    标签: angular session angular6


    【解决方案1】:

    您可以使用 bn-ng-idle npm 在 Angular 应用程序中检测用户空闲/会话超时。这篇博文解释对你有帮助Learn how to Handle user idleness and session timeout in Angular

    npm install bn-ng-idle
    

    app.module.ts

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    
    import { AppComponent } from './app.component';
    import { BnNgIdleService } from 'bn-ng-idle'; // import bn-ng-idle service
    
    
    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        BrowserModule
      ],
      providers: [BnNgIdleService], // add it to the providers of your module
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    

    app.component.ts

    import { Component } from '@angular/core';
    import { BnNgIdleService } from 'bn-ng-idle'; // import it to your component
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
    
      constructor(private bnIdle: BnNgIdleService) { // initiate it in your component constructor
        this.bnIdle.startWatching(300).subscribe((res) => {
          if(res) {
              console.log("session expired");
          }
        })
      }
    }
    

    在上面的例子中,我用 300 秒(5 分钟) 调用了 startWatching(timeOutSeconds) 方法并订阅了 observable,一旦用户空闲 5 分钟,订阅方法就会得到使用 res 参数的值(布尔值)作为 true 调用。

    通过检查 res 是否为真,您可以显示会话超时对话框或消息。为简洁起见,我只是将消息记录到控制台。

    【讨论】:

    • 什么时候“res”的值为假?当调用在 subscribe 函数内部进行时, res 始终为 true。何时调用 resetTimer 和 stopTimer 方法?我正在尝试显示第一个警告,然后像上面一样最终会话过期,请您帮忙,因为没有得到足够的相同实现
    • 安装“bn-ng-idle”并执行上述步骤后,我收到错误“Uncaught TypeError: Object(...) is not a function”。我正在使用 Angular 6。知道为什么我会遇到这个问题吗?
    【解决方案2】:

    选项:1: angular-user-idle

    逻辑

    • 库正在等待用户处于非活动状态 1 分钟(60 秒)。

    • 如果检测到非活动状态,则触发 onTimerStart()
      返回 2 分钟(120 秒)的倒计时。

    • 如果用户没有通过 stopTimer() 停止计时器,则时间在 2 之后结束 分钟(120 秒)并且 onTimeout() 被触发。

    在 AppModule 中:

    @NgModule({
          imports: [
            BrowserModule,
    
            // Optionally you can set time for `idle`, `timeout` and `ping` in seconds.
            // Default values: `idle` is 600 (10 minutes), `timeout` is 300 (5 minutes) 
            // and `ping` is 120 (2 minutes).
            UserIdleModule.forRoot({idle: 600, timeout: 300, ping: 120})
          ],
          declarations: [AppComponent],
          bootstrap: [AppComponent]
        })
    
    In any of your core componets:
    
        ngOnInit() {
            //Start watching for user inactivity.
            this.userIdle.startWatching();
    
            // Start watching when user idle is starting.
            this.userIdle.onTimerStart().subscribe(count => console.log(count));
    
            // Start watch when time is up.
            this.userIdle.onTimeout().subscribe(() => console.log('Time is up!'));
          }
    

    奖励: 您可以使用“ping”在给定的时间间隔(例如每 10 分钟)内请求刷新令牌。

    选项:2:使用ngrx

    请参考链接中的文章: https://itnext.io/inactivity-auto-logout-w-angular-and-ngrx-3bcb2fd7983f

    【讨论】:

    • 它只工作 1 分钟空闲.. 我已经设置了这个设置 UserIdleModule.forRoot({ idle: 1800, timeout: 600, ping: 1800 }),但它仍然在几分钟内注销仅..如何仅在用户空闲 30 分钟时设置超时
    【解决方案3】:

    您可以在主组件或父组件上使用以下代码。假设这是在 管理父组件,并假设您有身份验证服务,所以 您可以知道用户是否已登录

    声明变量

       userActivity;
       userInactive: Subject<any> = new Subject();
    

    在构造函数中或者在ngOnInit上添加

    this.setTimeout();
     this.userInactive.subscribe(() => {
       this.logout();
     });  
     logout() {
     this.authService.logout();
     this.authService.redirectLogoutUser();
    }
    

    最后添加以下方法

        setTimeout() {
        this.userActivity = setTimeout(() => {
          if (this.authService.isLoggedIn) {
            this.userInactive.next(undefined);
            console.log('logged out');
          }
        }, 420000);
      }
    
      @HostListener('window:mousemove') refreshUserState() {
        clearTimeout(this.userActivity);
        this.setTimeout();
      }
    

    【讨论】:

    • 在角度 8 中完美工作
    • 假设人们使用的是鼠标,但并非总是如此(触摸屏设备、屏幕阅读器、键盘等)
    【解决方案4】:

    超时后我在Angular8中添加了this.bnIdle.stopTimer(),因为当我访问同一个组件时,会出现时间故障。

    --> 我在 ngOnDestroy 中订阅和取消订阅,但是计时器没有停止。

    --> 找到了stopTimer 并实现了它,它对我来说工作得很好。希望对其他人有所帮助。

    this.bnIdle.startWatching(300).subscribe((res) => {
              if(res) {
                  console.log("session expired");
            this.bnIdle.stopTimer();
              }
            });
    

    【讨论】:

      【解决方案5】:
        timer = 0;      
      
        setInterval(() => {
            if (window.localStorage['userStoredToken']) {
              let clearTimer = localStorage.getItem('time-limit'); 
              if (clearTimer == 'clear-now') {
                this.timer = 0;
                setTimeout(() => {
                  localStorage.removeItem('time-limit');
                }, 5000);
              }
              this.timer++;
              if (this.timer > 2000) { // no of seconds after which user needs to logout
                this.logoutSessionTimeOut();
              }
            }
          }, 1000);
      
      
          @HostListener('mouseup', ['$event'])
          @HostListener('mousemove', ['$event'])
          onEvent(event: MouseEvent) {
              this.timer = 0;
              localStorage.setItem('time-limit', "clear-now");
          }
          @HostListener('document:keydown', ['$event']) onKeydownHandler(event: KeyboardEvent) {
              this.timer = 0;
              localStorage.setItem('time-limit', "clear-now");
          }
      

      【讨论】:

        【解决方案6】:

        我对我发现的一些方法(包括一些 npm 包)并不感到兴奋,所以我为 angular 设计了一些非常简单的方法。您只需在 app.component 中导入服务并调用它的 init() 方法。唯一棘手的部分是处理跨选项卡操作对话框的关闭。请务必注意,用于存储的 windoweventlisteners 仅在当前文档(也称为另一个窗口或选项卡)之外的存储发生更改时才会做出反应。

        import { Injectable, NgZone, OnDestroy } from '@angular/core';
        import { MatDialog, MatDialogConfig, MatDialogRef } from '@angular/material/dialog';
        import { Router } from '@angular/router';
        import { UserService } from '@common/services/user.service';
        import { takeWhile } from 'rxjs/operators';
        import { IdleTimeoutActions, IDLE_LOGOUT_TIME, IDLE_TIMEOUT_ACTION, IDLE_TIMEOUT_TIME } from './IdleTimeoutActions';
        import { InactivityTimeoutModalComponent } from './inactivity-timeout-modal/inactivity-timeout-modal.component';
        
        
        @Injectable({
          providedIn: 'root'
        })
        export class IdleTimeoutService implements OnDestroy {
        
          alive = true;
          interval;
          timeoutSetExpiredTime;
          boundUpdateExpiredTime;
          boundOnIdleTimeoutAction;
        
        
          inactivityDialogRef: MatDialogRef<InactivityTimeoutModalComponent>;
          dialogConfig: MatDialogConfig = {
            panelClass: ['confirmation-dialog', 'l-w400'],
            disableClose: true
          };
          dialogOpen = false;
        
          currentStatus;
          constructor(private dialog: MatDialog,
                      private userService: UserService,
                      private zone: NgZone,
                      private router: Router) {}
        
          init() {
            this.boundUpdateExpiredTime = this.updateExpiredTime.bind(this);
            this.boundOnIdleTimeoutAction = this.onIdleTimeoutAction.bind(this);
            this.userService.isLoggedIn().pipe(takeWhile(x => this.alive)).subscribe(userIsLoggedIn=> {
              if(userIsLoggedIn) {
                this.currentStatus = window.localStorage.getItem(IDLE_TIMEOUT_ACTION);
                if(this.currentStatus ===  IdleTimeoutActions.LOGOUT) { // if the user is logged in, reset the idletimeoutactions to null
                  window.localStorage.setItem(IDLE_TIMEOUT_ACTION, null);
                }
                window.addEventListener('storage', this.boundOnIdleTimeoutAction); // handle dialog action events from other tabs
                this.startTrackingIdleTime();
              }
            });
          }
        
          /**
           * Starts the interval that checks localstorage to determine if the idle expired time is at it's limit
           */
          startTrackingIdleTime() {
            this.addListeners();
            if(window.localStorage.getItem(IDLE_TIMEOUT_ACTION) !== IdleTimeoutActions.IDLE_TRIGGERED) {
              this.updateExpiredTime(0);
            }
            if(this.interval) {
              clearInterval(this.interval);
            }
            this.interval = setInterval(() => {
              const expiredTime = parseInt(localStorage.getItem('_expiredTime'), 10);
              if(expiredTime + (IDLE_LOGOUT_TIME * 1000) < Date.now()) {
                this.triggerLogout();
              } else if (expiredTime < Date.now()) {
                if(!this.dialogOpen) {
                  window.localStorage.setItem(IDLE_TIMEOUT_ACTION, IdleTimeoutActions.IDLE_TRIGGERED);
                  this.openIdleDialog();
                }
              }
            }, 1000);
          }
        
        
          triggerLogout() {
            this.removeListeners();
            // triggers other tabs to logout
            window.localStorage.setItem(IDLE_TIMEOUT_ACTION, IdleTimeoutActions.LOGOUT);
            this.dialog.closeAll();
            this.userService.logout();
            localStorage.setItem(IDLE_TIMEOUT_ACTION, null);
          }
        
          /**
           * Update the _exporedTime localStorage variable with a new time (timeout used to throttle)
           */
          updateExpiredTime(timeout = 300) {
            if(window.localStorage.getItem(IDLE_TIMEOUT_ACTION) !== IdleTimeoutActions.IDLE_TRIGGERED) {
              if (this.timeoutSetExpiredTime) {
                clearTimeout(this.timeoutSetExpiredTime);
              }
              this.timeoutSetExpiredTime = setTimeout(() => {
                this.zone.run(() => {
                  localStorage.setItem('_expiredTime', '' + (Date.now() + (IDLE_TIMEOUT_TIME * 1000)));
                });
              }, timeout);
            }
          }
        
          addListeners() {
            this.zone.runOutsideAngular(() => {
              window.addEventListener('mousemove', this.boundUpdateExpiredTime);
              window.addEventListener('scroll', this.boundUpdateExpiredTime);
              window.addEventListener('keydown', this.boundUpdateExpiredTime);
            });
          }
        
          removeListeners() {
            window.removeEventListener('mousemove', this.boundUpdateExpiredTime);
            window.removeEventListener('scroll', this.boundUpdateExpiredTime);
            window.removeEventListener('keydown', this.boundUpdateExpiredTime);
            window.removeEventListener('storage', this.boundOnIdleTimeoutAction);
            clearInterval(this.interval);
          }
        
        
        
          openIdleDialog() {
            this.dialogOpen = true;
            this.inactivityDialogRef = this.dialog.open(InactivityTimeoutModalComponent, this.dialogConfig);
            this.inactivityDialogRef.afterClosed().subscribe(action => {
              if(action === IdleTimeoutActions.CONTINUE) {
                this.updateExpiredTime(0);
                // trigger other tabs to close the modal
                localStorage.setItem(IDLE_TIMEOUT_ACTION, IdleTimeoutActions.CONTINUE);
                localStorage.setItem(IDLE_TIMEOUT_ACTION, null);
              } else if(action === IdleTimeoutActions.LOGOUT){
                this.triggerLogout();
              }
              this.dialogOpen = false;
            });
          }
        
          onIdleTimeoutAction = (event) => {
            if (event.storageArea === localStorage) {
              if(this.dialogOpen) {
                const action = localStorage.getItem(IDLE_TIMEOUT_ACTION);
                if(action === IdleTimeoutActions.LOGOUT) {
                  this.removeListeners();
                  this.dialog.closeAll();
                  this.router.navigate(['login']);
                } else if (action === IdleTimeoutActions.CONTINUE) {
                  this.updateExpiredTime(0);
                  this.inactivityDialogRef?.close(IdleTimeoutActions.CONTINUE);
                }
              }
            }
          }
        
          ngOnDestroy() {
            this.removeListeners();
            this.alive = false;
          }
        }
        

        【讨论】:

          【解决方案7】:

          这里有一些简短而通用的东西。

          监听 HTML 事件并报告空闲。结果以 rxjs 主题形式出现,或者如果需要,可以使用自定义 HTML 事件。

          import {Injectable, OnDestroy} from '@angular/core';
          import {Subject} from "rxjs";
          
          @Injectable({
              providedIn: 'root'
          })
          export class IdleDetectionService implements OnDestroy {
              public EVENT_WATCH_LIST: string[] = ['mousemove', 'mouseup', 'mousedown', 'scroll', 'keydown'];
              private readonly CHECK_FREQUENCY_MS = 1000 * 60;
              private idleCounterTarget: number | undefined = undefined;
              private eventActivityCounter: number = 0;
              private idleCounter: number = 0;
              private idleCheckTimerRef: any;
              private idleEvent: Subject<void> = new Subject();
          
              constructor() {
              }
          
              public start(timeToDetectIdleInMinutes: number): Subject<void> {
                  this.detachEventListeners();
                  this.attachEventListeners(timeToDetectIdleInMinutes);
                  return this.idleEvent;
              }
          
              detachEventListeners() {
                  if (this.idleEvent) {
                      this.idleEvent.complete();
                  }
                  clearInterval(this.idleCheckTimerRef);
                  this.EVENT_WATCH_LIST.forEach((eventName) => {
                      window.removeEventListener(eventName, this.onActivity.bind(this));
                  });
              }
          
              ngOnDestroy() {
                  this.detachEventListeners();
              }
          
              private attachEventListeners(timeToDetectIdleInMinutes: number) {
                  if (this.idleEvent) {
                      this.idleEvent.complete();
                      this.idleEvent = new Subject();
                  }
                  this.idleCounterTarget = timeToDetectIdleInMinutes;
                  // very important - when you passing the methods of the class pass them with .bind(this) otherwise it wont work!
                  this.EVENT_WATCH_LIST.forEach((eventName)=>{
                      window.addEventListener(eventName, this.onActivity.bind(this));
                  });
                  this.idleCheckTimerRef = setInterval(this.onIdleCheck.bind(this), this.CHECK_FREQUENCY_MS);
              }
          
              private onIdleCheck() {
                  if (this.eventActivityCounter === 0) {
                      this.idleCounter++;
                      if (this.idleCounterTarget !== undefined && this.idleCounter >= this.idleCounterTarget) {
                          setTimeout(() => {
                              // console.log('DETECTED IDLE, REPORT IT')
                              this.idleEvent.next();
                              // Note that this will continue to fire every minute
                              // the consumer might want to consume it with take(1)
                          });
                      }
                      // wait for more idle
                      return;
                  }
                  this.eventActivityCounter = 0;
                  this.idleCounter = 0;
              }
          
              private onActivity() {
                  // events could be very frequent, therefore we need something fast here
                  this.eventActivityCounter++;
              }
          
          }
           
          

          【讨论】:

            猜你喜欢
            • 2022-08-17
            • 1970-01-01
            • 2012-01-11
            • 2012-02-17
            • 2013-06-18
            • 2017-07-29
            • 1970-01-01
            • 2021-03-07
            • 2013-03-31
            相关资源
            最近更新 更多