Angular2 canActivate()调用异步函数


82

我正在尝试使用Angular2路由器防护来限制对我的应用程序中某些页面的访问。我正在使用Firebase身份验证。为了检查用户是否与火力地堡登录,我得叫.subscribe()FirebaseAuth同一个回调对象。这是警卫的代码:

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

导航到上面带有保护层的页面时authenticated,或者not authenticated将其打印到控制台(经过一段时间的等待,等待来自Firebase的响应)。但是,导航从未完成。另外,如果我没有登录,我将被重定向到该/login路由。因此,我遇到的问题是return true不会向用户显示请求的页面。我假设这是因为我正在使用回调,但是我无法弄清楚该如何做。有什么想法吗?


像这样导入Observable->从'rxjs / Observable'导入{Observable};
卡洛斯·普利戈

Answers:


124

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
君特Zöchbauer

在我的情况下,Observable不会运行。我没有看到任何控制台输出。但是,如果我有条件地返回布尔值(如在docs中),则会记录控制台。这是一个简单的Observable吗?
cortopy

@cortopyauth是可观察对象发出的值(可能是justtruefalse)。观察者在路由器订阅时执行。也许您的配置中缺少某些内容。
君特Zöchbauer

1
@günter-zöchbauer是的,谢谢。我没有意识到自己正在订阅订户。非常感谢您的回答!它工作的很好
cortopy

27

您可能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);
      });
    }
  }
}

2
这实际上是一个很好的答案,对我很有帮助。即使我有类似的问题,但被接受的答案也无法解决我的问题。这个做了
康斯坦丁

实际上,这是正确的答案!!!一种使用canActivate方法调用异步函数的好方法。
danilo


13

您可以返回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);
      }
    }

     })
   })
  }
}

1
那个新的Promise对象为我节省了:D谢谢。
canmustu '19

谢谢。该解决方案一直等到api调用响应然后再重定向。
菲利普·恩克

这看起来像一个显式Promise构造函数反模式的示例(stackoverflow.com/questions/23803743/…)。该代码示例建议getAccessRights()已经返回Promise,因此我将尝试直接将其返回return this.authService.getAccessRights().then...并返回布尔结果,而无需使用resolve
rob3c

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

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


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。
Pratik

2

就我而言,我需要处理不同的行为取决于响应状态错误。这就是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!')
      })
    );
  }
}

在某些情况下,这可能不起作用,至少对于operatornext部分而言。删除并添加旧商品,如下所示:tapmap

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

0

为了展示另一种实现方式。根据文档,并由其他答案提到,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);
  }
}


0

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

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
  }

这是我的认证gaurd(@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 } } },
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.