You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

已配置安全规则仍遭遇Cloud Firestore权限拒绝问题求助

问题:Firestore权限拒绝(PERMISSION_DENIED)- 集合组查询失败

我是Flutter x Firebase初学者,被这个问题困扰三天了,恳请帮忙解决并说明原因。

错误日志

/Firestore(30190): (25.0.0) [Firestore]: Listen for Query(target=Query( collectionGroup=leaves where status==pending order by name);limitType=LIMIT_TO_FIRST) failed: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}

我的代码

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

class AdminDashboardScreen extends StatefulWidget {
  const AdminDashboardScreen({super.key});

  @override
  _AdminDashboardScreenState createState() => _AdminDashboardScreenState();
}

class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
  bool isAdmin = false;
  Future<bool> _isAdmin(String uid) async {
    var doc = await FirebaseFirestore.instance.collection('admins').doc(uid).get();
    return doc.exists;
  }

  @override
  void initState() {
    super.initState();
    _checkIfAdmin();
  }

  Future<void> _checkIfAdmin() async {
    String uid = FirebaseAuth.instance.currentUser!.uid;
    bool admin = await _isAdmin(uid);
    setState(() {
      isAdmin = admin;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!isAdmin) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Admin Panel'),
        ),
        body: Center(child: Text('You do not have admin privileges.')),
      );
    }
return Scaffold(
  appBar: AppBar(
    title: Text('Admin Panel'),
  ),
  body: StreamBuilder(
    stream: FirebaseFirestore.instance.collectionGroup('leaves')
        .where('status', isEqualTo: 'pending').snapshots(),
    builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
      if (snapshot.connectionState == ConnectionState.waiting) {
        return Center(child: CircularProgressIndicator());
      }
      if (snapshot.hasError) {
        return Center(child: Text('Error: ${snapshot.error}'));
      }
      if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
        return Center(child: Text('No pending leave requests.'));
      }

      var leaveRequests = snapshot.data!.docs;
      return ListView.builder(
        itemCount: leaveRequests.length,
        itemBuilder: (context, index) {
          var leave = leaveRequests[index];
          return ListTile(
            title: Text('${leave['studentName']} is requesting for a leave'),
            subtitle: Text('Reason: ${leave['reason']}'),
            trailing: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                IconButton(
                  icon: Icon(Icons.check),
                  onPressed: () => _updateLeaveStatus(leave, 'approved'),
                ),
                IconButton(
                  icon: Icon(Icons.close),
                  onPressed: () => _updateLeaveStatus(leave, 'rejected'),
                ),
              ],
            ),
          );
        },
      );
    },
  ),
);
}

void _updateLeaveStatus(QueryDocumentSnapshot leave, String status) async {
    try {
      await leave.reference.update({'status': status});
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Leave request $status')));
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error updating status: $e')));
    }
  }
}

我的数据库安全规则

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Allow only authenticated users to read/write their own documents
    match /students/{studentId}/{documents=**} {
      allow read, write: if request.auth != null && request.auth.uid == studentId;
    }

    // Admin collection
    match /admins/{adminId} {
      allow read, write: if request.auth != null && request.auth.uid == adminId;
    }

    // Allow admins to read and write any student data
    match /students/{studentId}/{documents=**} {
      allow read, write: if request.auth != null && isAdmin(request.auth.uid);
    }
  }
}

解决方案及原因分析

问题原因

  1. 缺少集合组查询权限:你用了collectionGroup('leaves')查询所有名为leaves的子集合,但当前规则仅针对/students/{studentId}/{documents=**}路径设置权限,Firestore集合组查询需要单独匹配规则。
  2. 未实现isAdmin函数:规则中调用了isAdmin函数但没有定义,Firestore会直接判定该条件不成立,拒绝访问。

修复步骤

  1. 添加isAdmin验证函数
    在安全规则中定义验证管理员的函数:

    function isAdmin(uid) {
      return exists(/databases/$(database)/documents/admins/$(uid));
    }
    
  2. 补充集合组查询权限
    修改安全规则,添加针对所有leaves子集合的管理员访问权限:

    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        // 定义管理员验证函数
        function isAdmin(uid) {
          return exists(/databases/$(database)/documents/admins/$(uid));
        }
    
        // 学生仅能读写自己的数据
        match /students/{studentId}/{documents=**} {
          allow read, write: if request.auth != null && request.auth.uid == studentId;
        }
    
        // 管理员集合权限
        match /admins/{adminId} {
          allow read, write: if request.auth != null && request.auth.uid == adminId;
        }
    
        // 允许管理员访问所有students下的文档(包括子集合)
        match /students/{studentId}/{documents=**} {
          allow read, write: if request.auth != null && isAdmin(request.auth.uid);
        }
    
        // 允许管理员访问所有名为leaves的集合组
        match /{path=**}/leaves/{leaveId} {
          allow read, write: if request.auth != null && isAdmin(request.auth.uid);
        }
      }
    }
    

额外注意点

  • 集合组查询需要复合索引支持,运行代码后如果Firebase控制台提示创建索引,直接点击链接完成创建即可。
  • 确认当前登录用户的UID已存在于admins集合中,否则isAdmin函数会返回false,仍然触发权限拒绝。

内容的提问来源于stack exchange,提问作者Elle Bishop

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 14:57:04