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

Firestore权限拒绝:Flutter项目登出再登录后无法加载数据

Firebase + Flutter 权限问题排查与解决

问题描述

  • 触发场景:添加Firestore安全规则后,首次登录可正常加载数据;登出后再次登录,页面抛出权限错误:The caller does not have permission to execute the specified operation (Permission denied)
  • 异常现象:能正确获取currentUser信息,但无法加载展示社区、帖子数据

当前安全规则

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{document=**}{
      allow read,write: if request.auth !=null;
    }
}}

数据库结构

数据库包含三个核心集合:

  • users:存储用户基础信息
  • communities:存储社区数据,含members数组字段记录社区成员UID
  • posts:存储帖子数据,通过communityName字段关联所属社区

相关代码片段

FeedScreen 组件

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:reddit_tutorial/core/common/error_text.dart';
import 'package:reddit_tutorial/core/common/loader.dart';
import 'package:reddit_tutorial/core/common/post_card.dart';
import 'package:reddit_tutorial/features/auth/controlller/auth_controller.dart';
import 'package:reddit_tutorial/features/community/controller/community_controller.dart';
import 'package:reddit_tutorial/features/post/controller/post_controller.dart';

late ScrollController controller;

class FeedScreen extends ConsumerStatefulWidget {
  const FeedScreen({super.key});

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => _FeedScreenState();
}

class _FeedScreenState extends ConsumerState<FeedScreen> {
  @override
  Widget build(BuildContext context) {
    final user = ref.watch(userProvider)!;
    final isGuest = !user.isAuthenticated;
    if (!isGuest) {
      return ref.watch(userCommunitiesProvider).when(
            data: (communities) =>
                ref.watch(userPostsProvider(communities)).when(
                      data: (data) {
                        return Scrollbar(
                          child: ListView.builder(
                            itemCount: data.length,
                            itemBuilder: (BuildContext context, int index) {
                              final post = data[index];
                              return PostCard(post: post);
                            },
                          ),
                        );
                      },
                      error: (error, stackTrace) => ErrorText(error: error.toString()),
                      loading: () => const Loader(),
                    ),
            error: (error, stackTrace) => ErrorText(error: error.toString()),
            loading: () => const Loader(),
          );
    }
    return ref.watch(userCommunitiesProvider).when(
          data: (communities) => ref.watch(guestPostsProvider).when(
                data: (data) => ListView.builder(
                      itemCount: data.length,
                      itemBuilder: (BuildContext context, int index) => PostCard(post: data[index]),
                    ),
                error: (error, stackTrace) => ErrorText(error: error.toString()),
                loading: () => const Loader(),
              ),
          error: (error, stackTrace) => ErrorText(error: error.toString()),
          loading: () => const Loader(),
        );
  }
}

userCommunitiesProvider

final userCommunitiesProvider = StreamProvider((ref) {
  final communityController = ref.watch(communityControllerProvider.notifier);
  return communityController.getUserCommunities();
});

CommunityController.getUserCommunities 方法

Stream<List<Community>> getUserCommunities(String uid) {
    return _communities
        .where('members', arrayContains: uid)
        .orderBy('name', descending: false)
        .snapshots()
        .map((event) {
      List<Community> communities = [];
      for (var doc in event.docs) {
        communities.add(Community.fromMap(doc.data() as Map<String, dynamic>));
      }
      return communities;
    });
  }

userPostsProvider

final userPostsProvider =
    StreamProvider.family((ref, List<Community> communities) {
  final postController = ref.watch(postControllerProvider.notifier);
  return postController.fetchUserPosts(communities);
});

PostController.fetchUserPosts 方法

Stream<List<Post>> fetchUserPosts(List<Community> communities) {
    return _posts
        .where('communityName',
            whereIn: communities.map((e) => e.name).toList())
        .orderBy('createdAt', descending: true)
        .snapshots()
        .map(
          (event) => event.docs
              .map(
                (e) => Post.fromMap(
                  e.data() as Map<String, dynamic>,
                ),
              )
              .toList(),
        );
  }

问题根源

  1. 安全规则覆盖不全:当前规则仅开放了users集合的权限,但代码中实际访问的communities和posts集合没有配置权限,Firestore默认拒绝所有未明确授权的访问。
  2. 流订阅未随Auth状态更新:登出后Auth状态变更,但之前订阅的社区、帖子数据流没有重新获取权限上下文,旧的无权限流持续监听导致错误。

解决方案

1. 完善Firestore安全规则

给communities和posts集合添加对应权限,确保登录用户可访问所属社区及相关帖子:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // 用户集合权限
    match /users/{document=**}{
      allow read,write: if request.auth != null;
    }
    // 社区集合:仅允许成员读写
    match /communities/{communityId} {
      allow read,write: if request.auth != null && request.auth.uid in resource.data.members;
    }
    // 帖子集合:仅允许所属社区成员读写
    match /posts/{postId} {
      allow read,write: if request.auth != null 
                  && exists(/databases/$(database)/documents/communities/$(resource.data.communityName))
                  && request.auth.uid in get(/databases/$(database)/documents/communities/$(resource.data.communityName)).data.members;
    }
  }
}

2. 让数据流随Auth状态自动重订阅

修改userCommunitiesProvider,使其依赖Auth状态,当用户登录/登出时自动重建数据流:

final userCommunitiesProvider = StreamProvider((ref) {
  final authState = ref.watch(userProvider);
  final communityController = ref.watch(communityControllerProvider.notifier);
  
  return authState.when(
    data: (user) {
      if (user == null || !user.isAuthenticated) return Stream.value([]);
      return communityController.getUserCommunities(user.uid);
    },
    loading: () => Stream.value([]),
    error: (_, __) => Stream.value([]),
  );
});

3. 校验Auth状态更新逻辑

确保登出时正确清空userProvider状态,登录时及时同步最新用户信息,避免数据流使用过期的Auth上下文。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 07:44:56