在 auth-guard 的 canActivate 中使用 BehaviorSubject

Using BehaviorSubject in auth-guard's canActivate

我想要实现的目标:我想使用 BehaviorSubject 在我的应用程序中共享身份验证状态。我使用身份验证状态,例如在 auth-guard 内部,以防止用户在已经通过身份验证后访问 login/register 页面。

问题:因为BehaviorSubject有一个初始值,是false(未登录),看来auth-guard取的是这个第一个值,而不是等待uid 同步。

AuthInfo(授权状态存储):

export class AuthInfo {

  constructor(public uid: string) {}

  isLoggedIn() {
    return !!this.uid;
  }
}

AuthService:

@Injectable()
export class AuthService {

  static UNKNOWN_USER = new AuthInfo(null);
  authInfo$: BehaviorSubject<AuthInfo> = new BehaviorSubject<AuthInfo>(AuthService.UNKNOWN_USER);

  constructor(private af: AngularFire) {
    this.af.auth.subscribe(auth => {
      if (auth) {
        console.log('got the uid');
        this.authInfo$.next(new AuthInfo(auth.uid));
      } else {
        this.authInfo$.next(AuthService.UNKNOWN_USER);
      }
    });
  }

  logIn(email: string, password: string): Promise<FirebaseAuthState> {
    return this.af.auth.login({email: email, password: password});
  }
}

AuthGuard:

@Injectable()
export class AuthGuard implements CanActivate {

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

  canActivate(): Observable<boolean> {
    return this.authService.authInfo$.map(authInfo => {
      if (authInfo.isLoggedIn()) {
        this.router.navigate(['/user'])
      }
      return !authInfo.isLoggedIn();
    });
  }
}

因此 canActivate 的处理方式为 authInfo.isLoggedIn()false,几分之一秒后我在控制台中看到 Got the uid。任何想法如何防止第一个 false?我认为这里使用 BehaviorSubject 是正确的,因为它允许我们设置一个初始状态。但是,auth-guard 将始终收到 false(初始值)。紧接着

this.authInfo$.next(new AuthInfo(auth.uid));

将在 canActivate 方法已经完成时触发。

Guard 的 canActivate 方法,顾名思义,在尝试激活特定路由时进行解析。

据我从提供的代码中了解到,您试图在从服务器检索身份验证 uid 后将用户重定向到 /user 路由。为此,您需要在检索到身份验证 uid 后开始重定向到所需的路由 - 例如登录后,让您的警卫执行其工作,启用或拒绝对路由的访问。

整理好之后,下面是更改后的代码和结构的演练:

AuthInfo class:

// No changes.

授权服务:

@Injectable()
export class AuthService {

  static UNKNOWN_USER = new AuthInfo(null);
  authInfo$: BehaviorSubject<AuthInfo> = new BehaviorSubject<AuthInfo>(AuthService.UNKNOWN_USER);

  constructor(private af: AngularFire) { }

  logIn(email: string, password: string): Promise<FirebaseAuthState> {
    return this.af.auth.login({email: email, password: password});
  }

  getAuthInfo(): Observable<AuthInfo> {
    return this.af.auth.map(auth => {
      if(auth) {
        console.log('got the uid');
        let authInfo = new AuthInfo(auth.uid);
        this.authInfo$.next(authInfo);
        return authInfo;
      } 
      else {
        this.authInfo$.next(AuthService.UNKNOWN_USER);
        return AuthService.UNKNOWN_USER;
      }
    });
  }
}

AuthGuard:

@Injectable()
export class AuthGuard implements CanActivate {

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

  canActivate(): Observable<boolean> | boolean {

    // get the most recent value BehaviorSubject holds
    if (this.authService.authInfo$.getValue().isLoggedIn()) {
      // can access targeted route
      return true;
    }

    /*
    User is not logged in as stored authInfo indicates, 
    but in case the page has been reloaded, the stored value is lost, 
    and in order to get real auth status we will perform the server call,
    (authService.getAuthInfo method will automatically update the BehaviorSubject value, 
    and next time the protected route is accessed, no additional call will be made - until 
    the next reloading).
    */

    return this.authService.getAuthInfo()
        .map((authInfo: AuthInfo) => {
          if(authInfo.isLoggedIn()) {
            // can access targeted route
            return true;
          }

          this.router.navigate(['login']); // redirect to login screen
          return false;
        });
  }
}