我正在尝试在Angular + Firebase应用程序中实现身份验证保护。
我正在使用Firebase身份验证(FA)和Firebase Cloud Firestore(FCF)的组合来保存用户。因此,仅FA用户是不够的,我需要在FCF中找到同一用户才能考虑该用户已登录。
在注册期间,我将用户保存在FCF中的id
下,与FA中该用户的uid
相同。
在我的authentication.service
中:
export class AuthenticationService {
public authenticatedUserSubject: Subject<null | User> = new Subject <null | User>();
public constructor(
private readonly angularFireAuth: AngularFireAuth,
private readonly angularFirestore: AngularFirestore
) {
this.angularFireAuth.authState.subscribe(
(userInfo: null | UserInfo) => {
if (userInfo === null) {
this.authenticatedUserSubject.next(null);
} else {
this.angularFirestore.collection<User>('users').doc<User>(userInfo.uid).valueChanges().pipe(take(1)).subscribe(
(user: undefined | User): void => {
if (typeof user === 'undefined') {
this.authenticatedUserSubject.next(null);
} else {
this.authenticatedUserSubject.next(user);
}
}
);
}
}
);
}
}
根据我的理解,当服务初始化时,这段代码将开始侦听FA用户的更改,随后我将检查FCF用户并更新authenticatedUserSubject
。
在我的authenticated.guard
中:
public canActivate(activatedRouteSnapshot: ActivatedRouteSnapshot, routerStateSnapshot: RouterStateSnapshot): Observable<boolean> {
return this.authenticationService.authenticatedUserSubject.pipe(take(1)).pipe(
map(
(user: null | User): boolean => {
if (user === null) {
this.router.navigateByUrl('/sign-in');
return false;
} else {
return true;
}
}
)
);
}
问题:
null
的Subject,刷新页面将发出null
作为默认值,将用户扔到/sign-in
目标:
此外,如果有更好的方法来实现此功能,请告诉我!
答案 0 :(得分:0)
我想您可以像您所说的那样使用初始值为null的BehaviorSubject,但是您需要采取的防护措施是通过以下方式使用takeWhile
。
public canActivate(activatedRouteSnapshot: ActivatedRouteSnapshot, routerStateSnapshot: RouterStateSnapshot): Observable<boolean> {
return this.authenticationService.authenticatedUserSubject.pipe(
tap((user: User) => {
if(!user){
this.router.navigate(['/sign-up']);
}
}),
takeWhile((data) => data === null, true)
);
}
您知道方法需要布尔值,因此您可以在管道中的takeWhile
之前映射它。
希望这对您有用,因为我遇到了同样的问题,并且奏效了。
答案 1 :(得分:0)
您应该使用BehaviourSubject
而不是Subject
。主题不向用户发出最后发出的值。只有在订阅后Subject.next()时,Subject的订阅者才会获得价值。 BehaviourSubject
并非如此。订阅者将始终获得订阅中最后发出的值。借助此功能,我们可以尝试改进代码,看看问题是否解决:
像这样更改您的服务-
export class AuthenticationService {
public authenticatedUserSubject: BehaviorSubject<User> = new BehaviorSubject<User>(null);
public constructor(
private readonly angularFireAuth: AngularFireAuth,
private readonly angularFirestore: AngularFirestore
) {
combineLatest(this.angularFireAuth.authState,
this.angularFirestore.collection<User>('users').doc<User>(userInfo.uid).valueChanges()
)
.pipe(
tap(([userFromFireAuth, userFromFirestore]) => {
if (!userFromFireAuth) {
this.authenticatedUserSubject.next(null);
} else {
if (typeof userFromFireAuth === 'undefined') {
this.authenticatedUserSubject.next(null);
} else {
this.authenticatedUserSubject.next(userFromFireAuth);
}
}
})
).subscribe();
}
}
具有此功能将避免链接subscribe()
现在让我们这样重写canActivate:
public canActivate(activatedRouteSnapshot: ActivatedRouteSnapshot, routerStateSnapshot: RouterStateSnapshot): Observable<boolean> {
return this.authenticationService.authenticatedUserSubject
.pipe(
//this will wait until user becomes NOT NULL
skipWhile(user => !user),
tap(user => {
if (!user) {
//this will be useful when user logged out and trying to reach a page which requires authentication
//NOTICE - When page is NOT refreshed
this.router.navigate(['/sign-up']);
}
}),
take(1),
map(() => true)
);
}
让我们知道它是否有效。