Próbuję użyć zabezpieczeń routera Angular2, aby ograniczyć dostęp do niektórych stron w mojej aplikacji. Używam uwierzytelniania Firebase. W celu sprawdzenia, czy użytkownik jest zalogowany z Firebase, mam do rozmowy .subscribe()
na FirebaseAuth
obiekcie z zwrotnego. Oto kod dla strażnika:
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;
});
}
}
Kiedy przejść do strony, która ma osłonę na nim, albo authenticated
czy not authenticated
jest drukowany w konsoli (po pewnym opóźnieniem czeka na odpowiedź od Firebase). Jednak nawigacja nigdy nie jest zakończona. Jeśli nie jestem zalogowany, następuje przekierowanie na /login
trasę. Tak więc problem, który mam, return true
nie wyświetla żądanej strony użytkownikowi. Zakładam, że dzieje się tak, ponieważ używam wywołania zwrotnego, ale nie jestem w stanie dowiedzieć się, jak to zrobić inaczej. jakieś pomysły?
źródło
Odpowiedzi:
canActivate
musi zwrócić,Observable
który kończy:@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 } }
Jest
return
brakuje i używammap()
zamiastsubscribe()
ponieważsubscribe()
ZwracaSubscription
nie jestObservable
źródło
auth
to wartość emitowana przez obserwowalne (może być po prostutrue
lubfalse
). Obserwowalne jest wykonywane, gdy router subskrybuje to. Może czegoś brakuje w Twojej konfiguracji.Możesz użyć
Observable
do obsługi asynchronicznej części logicznej. Oto kod, który testuję na przykład: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); }); } } }
źródło
canActivate
może zwrócić,Promise
który rozwiązujeboolean
równieżźródło
Możesz zwrócić true | false jako obietnicę.
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); } } }) }) } }
źródło
return this.authService.getAccessRights().then...
zwrócić ją bezpośrednio za pomocą i zwrócić wynik boolowski bez zawijaniaresolve
.Aby rozwinąć najpopularniejszą odpowiedź. Auth API for AngularFire2 ma pewne zmiany. To jest nowa sygnatura do uzyskania 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; }); } }
Uwaga: to jest dość naiwny test. Możesz zarejestrować w konsoli instancję użytkownika, aby sprawdzić, czy chcesz przetestować pod kątem bardziej szczegółowego aspektu użytkownika. Powinien jednak przynajmniej pomóc w ochronie tras przed niezalogowanymi użytkownikami.
źródło
W najnowszej wersji AngularFire działa następujący kod (powiązany z najlepszą odpowiedzią). Zwróć uwagę na użycie metody „potoku”.
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; } }) ); } }
źródło
W moim przypadku musiałem poradzić sobie z różnymi zachowaniami zależnymi od błędu statusu odpowiedzi. Tak to działa u mnie z 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!') }) ); } }
W niektórych przypadkach może to nie działać, przynajmniej
next
częśćtap
operatora . Usuń go i dodaj stary towar,map
jak poniżej: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!') }) ); }
źródło
Aby pokazać inny sposób realizacji. Zgodnie z dokumentacją i wspomnianymi w innych odpowiedziach zwracany typ CanActivate może być również Obietnicą, która jest przekształcana na wartość logiczną.
Uwaga : Pokazany przykład jest zaimplementowany w Angular 11, ale ma zastosowanie do wersji Angular 2+.
Przykład:
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); } }
źródło
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 }
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 } } },
źródło