Angular2 canActivate () gọi hàm async


82

Tôi đang cố gắng sử dụng bộ bảo vệ bộ định tuyến Angular2 để hạn chế quyền truy cập vào một số trang trong ứng dụng của mình. Tôi đang sử dụng Xác thực Firebase. Để kiểm tra xem một người dùng đăng nhập với căn cứ hỏa lực, tôi phải gọi .subscribe()trên FirebaseAuthđối tượng với một callback. Đây là mã cho người bảo vệ:

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

Khi điều hướng đến một trang có bảo vệ trên đó authenticatedhoặc not authenticatedđược in ra bảng điều khiển (sau một thời gian chờ đợi phản hồi từ firebase). Tuy nhiên, việc điều hướng không bao giờ hoàn thành. Ngoài ra, nếu tôi chưa đăng nhập, tôi sẽ được chuyển hướng đến /logintuyến đường. Vì vậy, vấn đề tôi đang gặp phải là return truekhông hiển thị trang được yêu cầu cho người dùng. Tôi giả sử điều này là do tôi đang sử dụng một lệnh gọi lại, nhưng tôi không thể tìm ra cách làm khác. Có suy nghĩ gì không?


import Observable như thế này -> import {Observable} từ 'rxjs / Observable';
Carlos Pliego

Câu trả lời:


124

canActivatecần trả lại một Observablehoàn thành:

@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
    }
}

Còn returnthiếu và tôi sử dụng map()thay subscribe()vì vì subscribe()trả về SubscriptionmộtObservable


bạn có thể chỉ cách sử dụng lớp này trong các thành phần khác?

Không chắc chắn những gì bạn có ý nghĩa. Bạn sử dụng điều này với các tuyến chứ không phải các thành phần. Xem angle.io/docs/ts/latest/guide/router.html#!#guards
Günter Zöchbauer,

Observable không chạy trong trường hợp của tôi. Tôi không thấy bất kỳ đầu ra bảng điều khiển nào. Tuy nhiên, nếu tôi trả về booleans có điều kiện (như trong tài liệu), bảng điều khiển sẽ được ghi lại. This.auth có phải là một quan sát đơn giản?
cortopy

@cortopy authlà một giá trị được phát ra bởi giá trị có thể quan sát được (có thể chỉ là truehoặc false). Có thể quan sát được thực thi khi bộ định tuyến đăng ký với nó. Có thể một cái gì đó bị thiếu trong cấu hình của bạn.
Günter Zöchbauer

1
@ günter-zöchbauer vâng, cảm ơn vì điều đó. Tôi không nhận ra mình đang đăng ký một người đăng ký. Cảm ơn rất nhiều cho câu trả lời! Nó hoạt động tuyệt vời
cortopy

27

Bạn có thể sử dụng Observableđể xử lý phần logic không đồng bộ. Đây là mã tôi kiểm tra ví dụ:

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
Đó thực sự là một câu trả lời tốt thực sự đã giúp tôi. Mặc dù tôi đã có một câu hỏi tương tự nhưng câu trả lời được chấp nhận không giải quyết được vấn đề của tôi. Cái này đã làm
Konstantin

Trên thực tế, đây là câu trả lời đúng !!! Một cách tốt để sử dụng phương thức canActivate gọi một hàm không đồng bộ.
danilo

18

canActivatecó thể trở lại một Promisemà giải quyết một booleanquá


13

Bạn có thể trả về true | false như một lời hứa.

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
Đối tượng Promise mới đó đã cứu tôi: D Cảm ơn.
canmustu

Cảm ơn bạn. Giải pháp này đợi cho đến khi cuộc gọi api phản hồi và sau đó chuyển hướng .. hoàn hảo.
Philip Enc

Điều này trông giống như một ví dụ về phản vật chất của phương thức tạo Promise rõ ràng ( stackoverflow.com/questions/23803743/… ). Ví dụ mã đề xuất getAccessRights () trả về một Lời hứa đã có, vì vậy tôi sẽ thử trực tiếp trả lại nó cùng với return this.authService.getAccessRights().then...và trả về kết quả boolean mà không cần gói lại resolve.
rob3c

6

Để mở rộng câu trả lời phổ biến nhất. API Auth cho AngularFire2 có một số thay đổi. Đây là chữ ký mới để đạt được 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;
    });
  }
}

Lưu ý: Đây là một thử nghiệm khá ngây thơ. Bạn có thể điều khiển ghi nhật ký cá thể Người dùng để xem liệu bạn có muốn kiểm tra đối với một số khía cạnh chi tiết hơn của người dùng hay không. Nhưng ít nhất sẽ giúp bảo vệ các tuyến đường chống lại người dùng chưa đăng nhập.


4

Trong phiên bản AngularFire mới nhất, đoạn mã sau hoạt động (liên quan đến câu trả lời hay nhất). Lưu ý việc sử dụng phương pháp "tẩu".

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


Tôi có thêm 1 lệnh gọi XHR sau isLoggedIn () và kết quả của XHR được sử dụng trong lệnh gọi XHR thứ 2. Làm thế nào để có lệnh gọi ajax thứ 2 sẽ chấp nhận kết quả đầu tiên? Ví dụ bạn đưa ra khá dễ dàng, bạn có thể cho tôi biết cách sử dụng bản đồ nếu tôi cũng có một ajax khác.
Pratik

2

Trong trường hợp của tôi, tôi cần phải xử lý các hành vi khác nhau tùy thuộc vào lỗi trạng thái phản hồi. Đây là cách nó hoạt động đối với tôi với 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!')
      })
    );
  }
}

Trong một số trường hợp, điều này có thể không hoạt động, ít nhất là nextmột phần của taptoán tử . Xóa nó và thêm những thứ cũ mapnhư dưới đây:

  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

Để chỉ ra một cách thực hiện khác. Theo tài liệu , và được đề cập bởi các câu trả lời khác, loại trả về CanActivate cũng có thể là một Promise phân giải thành boolean.

Lưu ý : Ví dụ hiển thị được triển khai trong Angular 11, nhưng có thể áp dụng cho các phiên bản Angular 2+.

Thí dụ:

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

sử dụng async await ... bạn đợi lời hứa giải quyết

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
  }

Đây là gaurd auth của tôi (@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
  }

tuyến đường là

    { path: 'userEvent', component: NpmeUserEvent, canActivate: [AuthGuard], data: {  security: { semester: true } } },
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.