【问题标题】:Angular2 canActivate() calling async functionAngular2 canActivate() 调用异步函数
【发布时间】:2016-11-20 09:30:59
【问题描述】:

我正在尝试使用 Angular2 路由器防护来限制对我应用程序中某些页面的访问。我正在使用 Firebase 身份验证。为了检查用户是否使用 Firebase 登录,我必须通过回调调用 FirebaseAuth 对象上的 .subscribe()。这是守卫的代码:

import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AngularFireAuth } from "angularfire2/angularfire2";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private auth: AngularFireAuth, private router: Router) {}

    canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable<boolean>|boolean {
        this.auth.subscribe((auth) => {
            if (auth) {
                console.log('authenticated');
                return true;
            }
            console.log('not authenticated');
            this.router.navigateByUrl('/login');
            return false;
        });
    }
}

当导航到有保护的页面时,authenticatednot authenticated 会打印到控制台(在等待来自 firebase 的响应一段时间后)。但是,导航永远不会完成。另外,如果我没有登录,我会被重定向到/login 路由。所以,我遇到的问题是return true 没有向用户显示请求的页面。我假设这是因为我正在使用回调,但我无法弄清楚如何做到这一点。有什么想法吗?

【问题讨论】:

  • 像这样导入 Observable -> import { Observable } from 'rxjs/Observable';

标签: typescript angular angular2-routing


【解决方案1】:

canActivate 需要返回一个完成的Observable

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private auth: AngularFireAuth, private router: Router) {}

    canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable<boolean>|boolean {
        return this.auth.map((auth) => {
            if (auth) {
                console.log('authenticated');
                return true;
            }
            console.log('not authenticated');
            this.router.navigateByUrl('/login');
            return false;
        }).first(); // this might not be necessary - ensure `first` is imported if you use it
    }
}

缺少return,我使用map()而不是subscribe(),因为subscribe()返回Subscription而不是Observable

【讨论】:

  • 你能展示如何在其他组件中使用这个类吗?
  • 不确定你的意思。您可以将其用于路由,而不是组件。见angular.io/docs/ts/latest/guide/router.html#!#guards
  • Observable 在我的情况下无法运行。我没有看到任何控制台输出。但是,如果我有条件地返回布尔值(如在文档中),控制台会被记录。 this.auth 是一个简单的 Observable 吗?
  • @cortopy auth 是 observable 发出的值(可能只是 truefalse)。 observable 在路由器订阅它时执行。您的配置中可能缺少某些内容。
  • @günter-zöchbauer 是的,谢谢。我没有意识到我正在订阅订阅者。非常感谢您的回答!效果很好
【解决方案2】:

您可以使用Observable 来处理异步逻辑部分。以下是我测试的代码:

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { DetailService } from './detail.service';

@Injectable()
export class DetailGuard implements CanActivate {

  constructor(
    private detailService: DetailService
  ) {}

  public canActivate(): boolean|Observable<boolean> {
    if (this.detailService.tempData) {
      return true;
    } else {
      console.log('loading...');
      return new Observable<boolean>((observer) => {
        setTimeout(() => {
          console.log('done!');
          this.detailService.tempData = [1, 2, 3];
          observer.next(true);
          observer.complete();
        }, 1000 * 5);
      });
    }
  }
}

【讨论】:

  • 这实际上是一个很好的答案,对我很有帮助。即使我有类似的问题,但接受的答案并没有解决我的问题。这个做了
  • 其实这才是正确的答案!!!使用 canActivate 方法调用异步函数的好方法。
【解决方案3】:

canActivate 可以返回一个 Promise 也解析一个 boolean

【讨论】:

    【解决方案4】:

    您可以将 true|false 作为承诺返回。

    import {Injectable} from '@angular/core';
    import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
    import {Observable} from 'rxjs';
    import {AuthService} from "../services/authorization.service";
    
    @Injectable()
    export class AuthGuard implements CanActivate {
      constructor(private router: Router, private authService:AuthService) { }
    
      canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
      return new Promise((resolve, reject) => {
      this.authService.getAccessRights().then((response) => {
        let result = <any>response;
        let url = state.url.substr(1,state.url.length);
        if(url == 'getDepartment'){
          if(result.getDepartment){
            resolve(true);
          } else {
            this.router.navigate(['login']);
            resolve(false);
          }
        }
    
         })
       })
      }
    }
    

    【讨论】:

    • 那个新的 Promise 对象救了我 :D 谢谢。
    • 谢谢。此解决方案等到 api 调用响应然后重定向.. 完美。
    • 这看起来像是显式 Promise 构造函数反模式 (stackoverflow.com/questions/23803743/…) 的示例。代码示例表明 getAccessRights() 已经返回了一个 Promise,所以我会尝试使用 return this.authService.getAccessRights().then... 直接返回它并返回布尔结果而不用 resolve 包装。
    【解决方案5】:

    在最新版本的 AngularFire 中,以下代码有效(与最佳答案相关)。注意“管道”方法的使用。

    import { Injectable } from '@angular/core';
    import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
    import {AngularFireAuth} from '@angular/fire/auth';
    import {map} from 'rxjs/operators';
    import {Observable} from 'rxjs';
    
    @Injectable({
      providedIn: 'root'
    })
    export class AuthGuardService implements CanActivate {
    
      constructor(private afAuth: AngularFireAuth, private router: Router) {
      }
    
      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
        return this.afAuth.authState.pipe(
          map(user => {
            if(user) {
              return true;
            } else {
              this.router.navigate(['/login']);
              return false;
            }
          })
        );
      }
    }

    【讨论】:

    • 在 isLoggedIn() 之后我还有 1 个 XHR 调用,并且 XHR 的结果用于第二个 XHR 调用。如何进行第二个将接受第一个结果的 ajax 调用?你给出的例子很简单,如果我也有另一个 ajax,你能告诉我如何使用 map。
    【解决方案6】:

    扩展最受欢迎的答案。 AngularFire2 的 Auth API 有一些变化。这是实现 AngularFire2 AuthGuard 的新签名:

    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs/Observable';
    import { AngularFireAuth } from 'angularfire2/auth';
    import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
    
    @Injectable()
    export class AuthGuardService implements CanActivate {
    
      constructor(
        private auth: AngularFireAuth,
        private router : Router
      ) {}
    
      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):Observable<boolean>|boolean {
        return this.auth.authState.map(User => {
          return (User) ? true : false;
        });
      }
    }
    

    注意:这是一个相当幼稚的测试。您可以通过控制台记录 User 实例,以查看是否要针对用户的某些更详细的方面进行测试。但至少应该有助于保护路由免受未登录用户的攻击。

    【讨论】:

      【解决方案7】:

      为了展示另一种实现方式。根据documentation,以及其他答案提到的 CanActivate 返回类型也可以是解析为布尔值的 Promise。

      注意:所示示例在 Angular 11 中实现,但适用于 Angular 2+ 版本。

      示例:

      import {
        Injectable
      } from '@angular/core';
      import {
        ActivatedRouteSnapshot,
        CanActivate,
        CanActivateChild,
        Router,
        RouterStateSnapshot,
        UrlTree
      } from '@angular/router';
      import {
        Observable
      } from 'rxjs/Observable';
      import {
        AuthService
      } from './auth.service';
      
      @Injectable()
      export class AuthGuardService implements CanActivate, CanActivateChild {
        constructor(private authService: AuthService, private router: Router) {}
      
        canActivate(
          route: ActivatedRouteSnapshot, state: RouterStateSnapshot
        ): Observable < boolean | UrlTree > | Promise < boolean | UrlTree > | boolean | UrlTree {
          return this.checkAuthentication();
        }
      
        async checkAuthentication(): Promise < boolean > {
          // Implement your authentication in authService
          const isAuthenticate: boolean = await this.authService.isAuthenticated();
          return isAuthenticate;
        }
      
        canActivateChild(
          childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot
        ): Observable < boolean | UrlTree > | Promise < boolean | UrlTree > | boolean | UrlTree {
          return this.canActivate(childRoute, state);
        }
      }

      【讨论】:

        【解决方案8】:

        在我的情况下,我需要处理不同的行为取决于响应状态错误。这就是我使用 RxJS 6+ 的方式:

        @Injectable()
        export class AuthGuard implements CanActivate {
        
          constructor(private auth: AngularFireAuth, private router: Router) {}
        
          public canActivate(
            route: ActivatedRouteSnapshot,
            state: RouterStateSnapshot
          ): Observable<boolean> | boolean {
            return this.auth.pipe(
              tap({
                next: val => {
                  if (val) {
                    console.log(val, 'authenticated');
                    return of(true); // or if you want Observable replace true with of(true)
                  }
                  console.log(val, 'acces denied!');
                  return of(false); // or if you want Observable replace true with of(true)
                },
                error: error => {
                  let redirectRoute: string;
                  if (error.status === 401) {
                    redirectRoute = '/error/401';
                    this.router.navigateByUrl(redirectRoute);
                  } else if (error.status === 403) {
                    redirectRoute = '/error/403';
                    this.router.navigateByUrl(redirectRoute);
                  }
                },
                complete: () => console.log('completed!')
              })
            );
          }
        }
        

        在某些情况下,这可能不起作用,至少是 tap operatornext 部分。删除它并添加旧好的map,如下所示:

          public canActivate(
            route: ActivatedRouteSnapshot,
            state: RouterStateSnapshot
          ): Observable<boolean> | boolean {
            return this.auth.pipe(
              map((auth) => {
                if (auth) {
                  console.log('authenticated');
                  return true;
                }
                console.log('not authenticated');
                this.router.navigateByUrl('/login');
                return false;
              }),
              tap({
                error: error => {
                  let redirectRoute: string;
                  if (error.status === 401) {
                    redirectRoute = '/error/401';
                    this.router.navigateByUrl(redirectRoute);
                  } else if (error.status === 403) {
                    redirectRoute = '/error/403';
                    this.router.navigateByUrl(redirectRoute);
                  }
                },
                complete: () => console.log('completed!')
              })
            );
          }
        

        【讨论】:

          【解决方案9】:

          使用异步等待...您等待承诺解决

          async getCurrentSemester() {
              let boolReturn: boolean = false
              let semester = await this.semesterService.getCurrentSemester().toPromise();
              try {
          
                if (semester['statusCode'] == 200) {
                  boolReturn = true
                } else {
                  this.router.navigate(["/error-page"]);
                  boolReturn = false
                }
              }
              catch (error) {
                boolReturn = false
                this.router.navigate(["/error-page"]);
              }
              return boolReturn
            }
          

          这是我的身份验证器 (@angular v7.2)

          async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
              let security: any = null
              if (next.data) {
                security = next.data.security
              }
              let bool1 = false;
              let bool2 = false;
              let bool3 = true;
          
              if (this.webService.getCookie('token') != null && this.webService.getCookie('token') != '') {
                bool1 = true
              }
              else {
                this.webService.setSession("currentUrl", state.url.split('?')[0]);
                this.webService.setSession("applicationId", state.root.queryParams['applicationId']);
                this.webService.setSession("token", state.root.queryParams['token']);
                this.router.navigate(["/initializing"]);
                bool1 = false
              }
              bool2 = this.getRolesSecurity(next)
              if (security && security.semester) {
                // ----  watch this peace of code
                bool3 = await this.getCurrentSemester()
              }
          
              console.log('bool3: ', bool3);
          
              return bool1 && bool2 && bool3
            }
          

          路线是

              { path: 'userEvent', component: NpmeUserEvent, canActivate: [AuthGuard], data: {  security: { semester: true } } },
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-25
            • 1970-01-01
            • 1970-01-01
            • 2013-03-28
            • 2018-12-13
            相关资源
            最近更新 更多