Firestore规则在playground生效但应用报权限不足如何排查?
问题原因与解决方案
1. 你的安全规则确实存在缺陷
核心问题有两个:
- 规则里存在不必要的
get()操作:isOwner()中对resource.data.owner执行get()完全多余,直接对比DocumentReference即可;isMember()中get(/databases/$(database)/documents/projects/$(project))更是多此一举,当前匹配的项目文档数据可以直接通过resource.data获取,不需要额外读。 - 规则校验逻辑和查询条件没有对齐:你给
projects集合的读权限要求是用户是所有者或在flatMembers数组内,但你实际查询用的是members数组的array-contains-any条件,安全规则无法识别这两个字段的关联,也无法为集合查询批量执行文档内的get()操作来校验权限。
2. 为什么Playground测试通过但实际运行报错
你在Playground中测试的是单个项目文档的读操作,单文档读场景下安全规则允许执行get()等额外读操作校验权限,所以符合预期。但实际项目列表页是集合查询,Firestore为了保证查询性能,要求集合查询的条件必须直接匹配规则的约束,不能遍历所有候选文档逐一执行get()校验,所以直接拒绝了你的请求。你能正常打开单个项目页面也验证了这一点:单文档读不受这个限制。
3. 这类权限问题的调试方法
- 开启安全规则日志:在Firebase控制台的安全规则页面可以开启日志,所有请求的匹配过程、失败的规则行、传入的参数都会被记录,能快速定位问题。
- 分场景测试:除了单文档操作,一定要单独测试集合查询场景的规则有效性,不要只依赖单文档测试结果。
- 尽量避免在集合层级的读规则中使用
get()、exists()这类额外读操作,尽可能把权限判断所需的字段直接存在当前文档中,让查询条件可以直接和规则对齐。
修复步骤
第一步:修改安全规则
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // 复用当前用户的引用逻辑 function currentUserRef() { return /databases/$(database)/documents/users/$(request.auth.uid); } match /projects/{project} { function isOwner() { // 直接对比引用,去掉多余的get() return resource.data.owner == currentUserRef(); } function isMember() { // 直接取当前文档的flatMembers字段,不需要额外get return currentUserRef() in resource.data.flatMembers; } allow create: if request.auth != null; allow read: if request.auth != null && (isOwner() || isMember()); allow write: if isOwner(); match /sprints/{sprint} { allow read: if request.auth != null; allow create, write: if request.resource.data.startAt < request.resource.data.endAt; } match /backlog/{task} { allow read, create: if request.auth != null; allow write: if isMember(); } } match /users/{user} { allow read, write: if request.auth != null; allow create; } } }
第二步:修改查询代码,和规则对齐
你只需要把查询条件改为匹配flatMembers字段即可,不需要再查members数组:
return this.firestore.collection<Project>('projects', query => { const currentUserRef = this.authService.getCurrentUserRef(user); return query .where('flatMembers', 'array-contains', currentUserRef) .where('archived', '==', archived); }) .snapshotChanges() .pipe(mergeMap(projects => { return combineLatest(projects.map(project => { const data = project.payload.doc.data(); return this.userService.one(project.payload.doc.data().owner.id).pipe(map(owner => ({ id: project.payload.doc.id, name: data.name, owner: owner?.displayName ?? 'Unknown', ownerId: owner.uid, description: data.description, status: data.status, archived: data.archived }))); })); }));
内容的提问来源于stack exchange,提问作者KittyCat
相关产品推荐
相关产品推荐

