Flutter Web管理员创建新用户后Firestore权限报错排查
问题描述
Flutter Web项目中通过custom claims为用户配置admin角色,管理员账号登录后原本可正常读写Firestore数据,但管理员通过邮箱密码方式调用Firebase Auth接口创建新用户后,就无法再访问Firestore数据,抛出异常:[cloud_firestore/permission-denied] Missing or insufficient permissions.
相关配置代码
Firestore安全规则
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { function isSignedIn() { return request.auth != null; } function getRole() { return request.auth.token.role; } function isOneOfRoles(array) { return isSignedIn() && (getRole() in array); } function isValidNewDoiTuong() { return isOneOfRoles(['admin','tp_kd','nv_kd']); } // 客户状态模块 match /tinhtrang_kh/{tinhtrang} { function isValidNewTinhTrangKH() { return isOneOfRoles(['admin','tp_kd']); } function onlyContentChanged() { // 确保dtID、roles字段未修改,且文档无新增字段 return request.resource.data.dtID == resource.data.dtID && request.resource.data.roles == resource.data.roles && request.resource.data.keys() == resource.data.keys(); } allow create: if isValidNewTinhTrangKH(); allow delete: if isOneOfRoles(['admin','tp_kd']); allow update: if isOneOfRoles( ['admin','tp_kd']) || (isOneOfRoles( ['tp_kd']) && onlyContentChanged()); allow read: if isSignedIn(); } // 用户模块 match /tk_users/{tkuser} { allow create: if isOneOfRoles(['admin']); allow delete: if isOneOfRoles(['admin']); allow update: if isOneOfRoles( ['admin']); allow read: if isSignedIn(); } } }
自定义角色Claim配置云函数
// 用户角色同步 exports.updateAccess = functions.firestore .document('tk_users/{userId}') .onUpdate(async (change, context) => { const newValue = change.after.data(); const customClaims = { role: newValue.role }; // 更新对应用户的自定义claims return admin.auth().setCustomUserClaims( context.params.userId, customClaims) .then(() => { console.log("Done!") }) .catch(error => { console.log(error); }); });
客户端认证逻辑
Future<AppUser?> signIn( {required String email, required String password, bool createAccount = false}) async { UserCredential userCreds; if (createAccount) { userCreds = await auth.createUserWithEmailAndPassword( email: email, password: password); } else { userCreds = await auth.signInWithEmailAndPassword( email: email, password: password); } User? user = userCreds.user; String? role = ''; if (!createAccount) { final idTokenResult = await user!.getIdTokenResult(); role = idTokenResult.claims?['role']; } return user == null ? null : AppUser(email: user.email ?? "", documentId: user.uid, role: role); }
故障原因
- 认证会话自动切换:客户端已登录管理员账号时调用
createUserWithEmailAndPassword,Firebase Auth会在新用户创建成功后自动将当前登录会话切换为新用户,原有管理员登录态直接失效。刚创建的新用户没有配置admin自定义声明,访问需要admin权限的Firestore路径必然会报权限不足。 - ID令牌缓存问题:就算后续手动切回管理员账号,客户端默认会缓存已拿到的ID令牌,如果不强制刷新,安全规则校验时读到的还是旧令牌里的角色信息,也可能触发权限报错。
- 现有逻辑还有两个额外隐患:
- 配置自定义声明的云函数只监听了
tk_users文档的更新事件,新用户文档创建时不会触发,新用户第一次创建后角色不会自动同步到Auth claims。 - 客户端创建账号的分支完全没有拉取角色、处理登录态切换的逻辑,创建完成后直接返回新用户对象,后续权限判断拿到的角色为空。
- 配置自定义声明的云函数只监听了
修复方法
- 把创建普通用户的逻辑迁移到云函数,通过Firebase Admin SDK完成用户创建、角色配置、
tk_users文档写入的全流程,全程在服务端执行,不会触发客户端管理员账号的登录态切换,从根源避免问题。 - 若暂时保留客户端创建用户的逻辑,创建成功后必须先调用
auth.signOut()登出新创建的用户,再重新用管理员账号登录;登录后调用user.getIdTokenResult(true)(传入true参数强制从服务端拉取最新令牌)获取最新的角色声明,不要使用本地缓存的令牌。 - 修改云函数触发条件,同时监听
tk_users文档的onCreate和onUpdate事件,保证新用户创建时也能自动同步角色到自定义声明。
内容的提问来源于stack exchange,提问作者Huong Tran
相关产品推荐
相关产品推荐

