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

使用Getx+Firebase手机号验证时登录/登出触发Firestore权限拒绝错误

问题:手机号验证登录/登出时触发Firestore权限拒绝,重启应用后恢复正常

现象与报错

通过手机号验证登录或登出应用时,触发Firestore权限拒绝错误,重启应用后可正常使用。报错信息如下:

Exception has occurred.
FirebaseException ([cloud_firestore/permission-denied] The caller does not have permission to execute the specified operation.)

相关代码

Dart手机号验证控制器代码

class PhoneAuthController extends GetxController {

  static PhoneAuthController instance = Get.find();
  late Rx<User?> _user;
  FirebaseAuth auth = FirebaseAuth.instance;

  CollectionReference subscribers = FirebaseFirestore.instance.collection("subscribers");
  
  @override
  void onReady(){
    super.onReady();
    _user = Rx<User?>(auth.currentUser);
    _user.bindStream(auth.userChanges());
    ever(_user, _initialScreen);
  }

  _initialScreen(User? user){
    if(user==null){
      Get.offAll(()=> const VerifyByNumber());
    } else {
      String number = user.phoneNumber.toString().substring(1);
      var subscriber = subscribers.doc(number);
      subscriber.update({"userId": user.uid});
      Get.offAll(()=>HomePage(phoneNumber: number,));
    }
  }

  void verifyPhoneNumber(String uPhoneNumber) {
    String number = uPhoneNumber.substring(1);
      auth.verifyPhoneNumber(
          timeout: const Duration(seconds: 120),
          phoneNumber: uPhoneNumber,
          verificationCompleted: (PhoneAuthCredential credential) {
            auth.signInWithCredential(credential).then((value) {});
          },
          verificationFailed: (FirebaseAuthException e) {
          },
          codeSent: (String id, int? token) {
            Get.off(() =>
                VerifyToCode(
                    phoneNumber: number,
                    verifyId: id,));
          },
          codeAutoRetrievalTimeout: (String verificationID) {});
  }

  void verifyCode(String uPhoneNumber, String verifyId, String smsCode) {
    Map<String, dynamic> subscriberToSave = {
      "phone": "+$uPhoneNumber",
      "name": null,
      "email": null,
      "password": null,
      "fcmToken": null,
      "date": DateTime.now()
    };
    try {
      FirebaseFirestore.instance.collection("subscribers").doc(uPhoneNumber).set(subscriberToSave);
      PhoneAuthCredential credential = PhoneAuthProvider.credential(verificationId: verifyId, smsCode: smsCode);
      auth.signInWithCredential(credential);   
      Get.offAll(VerificationSuccessfulScreen(number: uPhoneNumber));      
    } catch (e) {
      Get.snackbar("verifyCode, ERROR:", ">$e");
    }
  }

  void quitFirebaseAuth() {
    FirebaseAuth.instance.signOut();
  }
}

Firestore安全规则

service cloud.firestore {
  match /databases/{database}/documents {
    match /subscribers/{subscriber} {
    
        allow get, write: if isSignIn() && 
        request.auth.uid == resource.data.userId && 
        request.auth.token.phone_number == resource.data.phone;
        
            match /sites/{site} {
            allow read, write: if isSignIn();
          match /messages/{message}{
            allow read, write: if isSignIn();
          }
        }
    }
    match /sites/{site} {
        allow read, write: if isSignIn();
    }
  } 
  
  function isSignIn(){
    return request.auth.uid != null;
  }
}

问题原因分析

  1. 登录时序错误:verifyCode方法中先执行Firestore的set操作,此时用户还未完成登录(auth.signInWithCredential是异步操作,未等待完成就执行写入),导致request.auth为空,触发isSignIn()校验失败。
  2. 权限规则逻辑冲突:subscribers文档的写入规则要求request.auth.uid == resource.data.userId,但首次创建文档时resource.data不存在,该条件永远不满足,导致首次写入失败。
  3. 登出状态同步延迟:登出后Getx的_user流未及时同步Auth状态,后续操作可能基于旧状态触发权限检查失败。

解决办法

1. 修正登录时序,等待Auth登录完成后操作Firestore

修改verifyCode方法,确保登录完成后再写入Firestore,且首次创建文档时就传入userId:

void verifyCode(String uPhoneNumber, String verifyId, String smsCode) {
  try {
    PhoneAuthCredential credential = PhoneAuthProvider.credential(verificationId: verifyId, smsCode: smsCode);
    // 等待登录操作完成
    auth.signInWithCredential(credential).then((userCredential) {
      User? user = userCredential.user;
      if (user != null) {
        Map<String, dynamic> subscriberToSave = {
          "phone": "+$uPhoneNumber",
          "name": null,
          "email": null,
          "password": null,
          "fcmToken": null,
          "date": DateTime.now(),
          "userId": user.uid // 首次创建即写入userId
        };
        FirebaseFirestore.instance.collection("subscribers").doc(uPhoneNumber).set(subscriberToSave);
        Get.offAll(VerificationSuccessfulScreen(number: uPhoneNumber));
      }
    });
  } catch (e) {
    Get.snackbar("verifyCode, ERROR:", ">$e");
  }
}

2. 修正Firestore规则,兼容首次创建文档场景

区分create和update操作,调整权限校验逻辑:

service cloud.firestore {
  match /databases/{database}/documents {
    match /subscribers/{subscriber} {
      // 首次创建文档:仅校验登录状态和手机号匹配
      allow create: if isSignIn() && request.resource.data.phone == request.auth.token.phone_number;
      // 更新/读取文档:校验登录状态、userId和手机号匹配
      allow get, update: if isSignIn() && request.auth.uid == resource.data.userId && request.auth.token.phone_number == resource.data.phone;
      
      match /sites/{site} {
        allow read, write: if isSignIn();
        match /messages/{message}{
          allow read, write: if isSignIn();
        }
      }
    }
    match /sites/{site} {
      allow read, write: if isSignIn();
    }
  } 
  
  function isSignIn(){
    return request.auth.uid != null;
  }
}

3. 优化登出后的状态同步

确保登出后立即跳转登录页,避免基于旧状态操作:

void quitFirebaseAuth() {
  FirebaseAuth.instance.signOut().then((_) {
    Get.offAll(() => const VerifyByNumber());
  });
}

4. 优化_initialScreen中的更新操作

先检查文档是否存在,再执行更新:

_initialScreen(User? user){
  if(user==null){
    Get.offAll(()=> const VerifyByNumber());
  } else {
    String number = user.phoneNumber.toString().substring(1);
    var subscriber = subscribers.doc(number);
    subscriber.get().then((docSnapshot) {
      if (docSnapshot.exists) {
        subscriber.update({"userId": user.uid});
      }
      Get.offAll(()=>HomePage(phoneNumber: number,));
    });
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 04:07:07