Firebase规则模拟通过但Google认证后客户端权限拒绝问题排查
我帮你梳理下这个权限拒绝问题的核心原因,主要集中在Firebase规则的拼写错误和客户端操作的不匹配上,具体如下:
1. Firebase规则中的致命拼写错误
你的规则里多次使用了auth.id,但Firebase Auth提供的用户唯一标识是auth.uid,auth.id这个属性根本不存在!这直接导致普通用户(非管理员)的读写权限条件永远不成立,触发permission denied。
比如原规则里的这两行:
".read":"root.child('users').child(auth.uid).child('roles/admin').val()===true || root.child('users').child(auth.id).child('id').val()===auth.uid", ".write":"!(root.child('users').child(auth.uid).exists()) || root.child('users').child(auth.uid).child('roles/admin').val()===true || root.child('users').child(auth.id).child('id').val()===auth.uid",
其中root.child('users').child(auth.id)这部分完全无效,因为auth.id是undefined,匹配不到任何节点。
修正后的规则
把所有auth.id替换为auth.uid,修正后的规则如下:
{ "rules": { ".read": "root.child('users').child(auth.uid).child('roles/admin').val() === true || root.child('users').child(auth.uid).child('id').val() === auth.uid", ".write": "!root.child('users').child(auth.uid).exists() || root.child('users').child(auth.uid).child('roles/admin').val() === true || root.child('users').child(auth.uid).child('id').val() === auth.uid" } }
2. 客户端操作的逻辑错误:update vs set
当检测到新用户(!user)时,你使用了ref.update(googleUser),但Firebase的update()方法只能修改已存在的节点,如果节点不存在,update()会直接失败,同时触发权限检查的问题。
新用户创建需要用set()方法,它可以创建新节点或替换现有节点,完全匹配你规则里「允许创建不存在的用户节点」的条件。
修正后的Angular代码片段
把ref.update(googleUser)替换为ref.set(googleUser),同时建议你把set的操作也纳入流中,确保动作完成后再分发成功action:
@Effect() loginGetUserInfo$ = this.actions$.pipe( ofType(AuthActionTypes.AUTH_LOGIN_GET_USER_INFO), map((action: AuthLoginGetUserInfo) => action.user), exhaustMap((googleUser: User) => { const ref = this.db.object('users/' + googleUser.uid); return ref.valueChanges().pipe( switchMap((user: User) => { if (!user) { console.log("Is a new user:", googleUser); // 用set创建新用户节点 return ref.set(googleUser).pipe( map(() => new AuthLoginSuccessful(googleUser)) ); } return of(new AuthLoginSuccessful(user)); }), catchError(error => of(new AuthLoginFailure(error))) ); }) );
3. 额外检查:确保认证状态稳定
另外,建议你在执行数据库操作前,确认用户的认证状态已经稳定。有时候Google认证完成后,Auth状态可能有短暂的延迟,导致auth.uid为null,触发权限拒绝。可以通过AngularFireAuth的authState流来确保用户已认证:
exhaustMap((googleUser: User) => { return this.afAuth.authState.pipe( take(1), // 只取最新的认证状态 switchMap(authUser => { if (!authUser) { throw new Error('User not authenticated'); } const ref = this.db.object('users/' + authUser.uid); return ref.valueChanges().pipe(/* 后续逻辑 */); }) ); })
总结
解决这个问题的核心步骤:
- 修正Firebase规则中的
auth.id拼写错误,改为auth.uid - 新用户创建时使用
set()而非update() - 确保数据库操作前用户已完成认证,
auth.uid有效
内容的提问来源于stack exchange,提问作者Jason Nguyen

