auth.service 路由守卫

Route guard with auth.service

我正在尝试创建一个路由保护来保护一些延迟加载的模块的安全,我有一个带有 BehaviorSubject 的身份验证服务,它保存当前用户和 JWT 令牌

当我调用守卫时,它首先获取当前用户的默认值,然后才尝试允许用户到达路线。

auth.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpResponse, HttpHeaders } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { map, skip, takeLast, last } from 'rxjs/operators';

import { User } from '../shared/user';
import { environment } from '../../environments/environment';


@Injectable()
export class AuthService {
  private loginUserToken: BehaviorSubject<string>;
  private currentUser: BehaviorSubject<User>;
  constructor(private http: HttpClient) {
    // service init
    this.loginUserToken = new BehaviorSubject(undefined);
    this.currentUser = new BehaviorSubject(undefined);
    this.loginUserToken.next(this.getTokenFromLocalStorege());
    if (this.loginUserToken.value != null) {
      this.getUserFromToken();
    }
  }
  /**
   * getLoginUser
   */
  public getLoginUserAsObservable() {
    return this.currentUser.asObservable();
  }
  public getLoginUserTokenAsObservable() {
    return this.loginUserToken.asObservable();
  }
  public async login(user: User): Promise<any> {
    // tslint:disable-next-line:no-shadowed-variable
    return new Promise<any>(async (resolve: any, reject: any) => {
      try {
        const result: any = await this.http.post(`${environment.server}/api/auth/login`, user).toPromise();
        if (result.massageCode === 1) {
          reject('bedUsername');
        } else if (result.massageCode === 2) {
          reject('bed password');
        } else {
          this.loginUserToken.next(result.token);
          this.getUserFromToken();
          this.saveTokenToLocalStorege(result.token);
          resolve();
        }
      } catch (error) {
        reject('error');
      }
    });
  }
  public getUserFromToken(): void {
    const headers = new HttpHeaders({
      'x-access-token': this.loginUserToken.value
    });
    this.http.get(`${environment.server}/api/auth/userFromToken`, { headers }).toPromise()
      .then((data: User) => {
        this.currentUser.next(data);
      })
      .catch((error) => {
        console.log(error);
      });
  }
  public isLogin(): Promise<boolean> {
    return new Promise((resolve, reject) => {
      this.currentUser.asObservable()
      .subscribe((data) => {
        if (data) {
          resolve(true);
        } else {
          resolve(false);
        }
      }).unsubscribe();
    });
  }
  public saveTokenToLocalStorege(token: string): void {
    localStorage.setItem('chanToken', token);
  }
  public getTokenFromLocalStorege(): string {
    return localStorage.getItem('chanToken');
  }
  public removeTokenFromLocalStrege(): void {
    localStorage.removeItem('chanToken');
  }
}

auth.guaed.ts:

import { Injectable } from '@angular/core';
import { CanActivate, CanLoad, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../auth.service';
import { Route } from '@angular/compiler/src/core';
import { last, map } from 'rxjs/operators';
@Injectable()
export class AuthGuard implements CanLoad {

  constructor(private authSerivce: AuthService, private router: Router) { }

  canLoad(route: Route): boolean | Observable<boolean> | Promise<boolean> {
    console.log('use gruad');
    return this.authSerivce.isLogin();
  }
}

那是因为 BehaviourSubject 的工作原理。当你 SubscribeBehaviourSubject 它立即 return 是最后一个值。这就是 BehaviourSubject 需要默认值的原因。

在你的情况下,一旦 AuthGuard 被激活,它将调用你的订阅 currentUser 的方法 isLogin 并且它将 return undefined (取决于 javascript 执行的顺序)。基本上你的 AuthGuard 不会等待你的函数 getUserFromToken() 完成。

当您使用 ReplaySubject 而不是 BehaviourSubject 时,您可以轻松解决此解决方案。

private currentUser: ReplaySubject<User> = new ReplaySubject(1)

用值 1 初始化 ReplaySubject,它将缓存您当前的用户,但当没有用户存在时不会触发。

现在你的 Authguard 将等待一个有效的 currentUser 值并且当 currentUser 未定义时不会触发