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

Xamarin集成Firebase Auth通过登录Token实现仅数据所有者可编辑数据

基于Token的邮箱登录用户自有数据权限控制实现方案

要实现仅数据所有者可编辑/删除自有数据,必须做两层防护:客户端前置校验减少无效请求,Firebase服务端安全规则做硬拦截。注意:客户端代码可被反编译篡改,服务端规则是安全底线,不能省略。

1. 改造数据模型,绑定所有者唯一标识

不要用邮箱作为所有者判断依据(用户可能修改邮箱,会导致权限丢失),用Firebase Auth生成的全局唯一Uid做关联。
先给StudentModel类新增属性:

public string OwnerUid { get; set; }

后续新建数据时,强制把当前登录用户的Uid写入这个字段,不允许前端传值覆盖。

2. 配置Realtime Database安全规则(核心)

打开Firebase控制台,进入Realtime Database的规则配置页,替换成下面的规则,发布后直接生效:

{
  "rules": {
    "StudentModel": {
      // 所有登录用户可读取列表/单条数据,需要做私有读的话可以把这行改成按Uid校验
      ".read": "auth != null",
      "$studentId": {
        // 写权限校验逻辑:
        // 1. 必须是登录状态
        // 2. 新建数据时,提交的OwnerUid必须和当前Token对应的用户Uid一致
        // 3. 修改/删除已有数据时,原数据的OwnerUid必须和当前用户Uid一致
        ".write": "auth != null && (!data.exists() || data.child('OwnerUid').val() == auth.uid) && newData.child('OwnerUid').val() == auth.uid"
      }
    }
  }
}

这套规则会直接在服务端拦截所有越权请求,哪怕有人篡改客户端代码伪造请求,没有对应权限也改不了数据。如果用到Firebase Storage存储图片,也要给存储桶配同逻辑的安全规则,按用户Uid校验文件操作权限。

3. 改造StudentRepository类

原来的代码用了无认证的全局FirebaseClient,没有携带用户身份Token,会被安全规则拦截。改造点如下:

  • 初始化仓储类时传入当前登录用户的身份Token和Uid,不要用全局无认证实例
  • 新增/修改数据时强制绑定当前用户Uid,不允许篡改所有者
  • Update/Delete方法加客户端前置校验,减少无效的越权请求
    改造后的完整代码:
using Firebase.Database; 
using Firebase.Storage; 
using FirebaseAdmin.Auth; 
using Google.Apis.Auth.OAuth2; 
using Newtonsoft.Json; 
using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace XamarinFirebaseApp {
    public class StudentRepository
    {
        private readonly string _userAuthToken;
        private readonly string _currentUserUid;
        private readonly FirebaseClient _firebaseClient;
        private readonly FirebaseStorage _firebaseStorage;

        // 初始化时传入当前登录用户的Token和Uid,登录成功后缓存这两个值即可
        public StudentRepository(string userAuthToken, string currentUserUid)
        {
            _userAuthToken = userAuthToken;
            _currentUserUid = currentUserUid;
            _firebaseClient = new FirebaseClient(
                "https://xamarinfireabse-default-rteb.firebaseio.com/",
                new FirebaseOptions
                {
                    AuthTokenAsyncFactory = () => Task.FromResult(_userAuthToken)
                });
            _firebaseStorage = new FirebaseStorage("xamarinfireabse.adrfspot.com");
        }

        public async Task<bool> Save(StudentModel student)
        {
            // 强制绑定当前用户为数据所有者,忽略前端传入的OwnerUid值
            student.OwnerUid = _currentUserUid;
            var data = await _firebaseClient.Child(nameof(StudentModel)).PostAsync(JsonConvert.SerializeObject(student));
            return !string.IsNullOrEmpty(data.Key);
        }

        public async Task<List<StudentModel>> GetAll()
        {
            return (await _firebaseClient.Child(nameof(StudentModel)).OnceAsync<StudentModel>()).Select(item
                => new StudentModel
            {
                Email = item.Object.Email,
                Name = item.Object.Name,
                Image = item.Object.Image,
                OwnerUid = item.Object.OwnerUid,
                Id = item.Key
            }).ToList();
        }

        public async Task<List<StudentModel>> GetAllByName(string name)
        {
            return (await _firebaseClient.Child(nameof(StudentModel))
                .OnceAsync<StudentModel>()).Select(item => new StudentModel
            {
                Email = item.Object.Email,
                Name = item.Object.Name,
                Image = item.Object.Image,
                OwnerUid = item.Object.OwnerUid,
                Id = item.Key
            }).Where(c=>c.Name.ToLower().Contains(name.ToLower())).ToList();
        }

        public async Task<StudentModel> GetById(string id)
        {
            return await _firebaseClient.Child(nameof(StudentModel) + "/" + id).OnceSingleAsync<StudentModel>();
        }

        public async Task<bool> Update(StudentModel student)
        {   
            // 客户端前置校验,提前拦截越权请求,真正的权限判定以服务端规则为准
            var existingStudent = await GetById(student.Id);
            if (existingStudent.OwnerUid != _currentUserUid)
            {
                throw new UnauthorizedAccessException("你无权修改这条数据");
            }
            // 强制保留原所有者Uid,防止篡改
            student.OwnerUid = _currentUserUid;
            await _firebaseClient.Child(nameof(StudentModel) + "/" + student.Id)
                .PutAsync(JsonConvert.SerializeObject(student));
            return true;
        }

        public async Task<bool> Delete(string id)
        {
            // 客户端前置校验
            var existingStudent = await GetById(id);
            if (existingStudent.OwnerUid != _currentUserUid)
            {
                throw new UnauthorizedAccessException("你无权删除这条数据");
            }
            await _firebaseClient.Child(nameof(StudentModel) + "/" + id).DeleteAsync();
            return true;
        }

        public async Task<string> Upload(Stream img, string fileName)
        {
            var image = await _firebaseStorage.Child("Images").Child(fileName).PutAsync(img);
            return image;
        }
    } 
}

4. 登录逻辑对接

用户通过邮箱完成登录后,从Firebase Auth的返回结果中取两个值:

  • 用户身份Token(Firebase Token):作为请求的身份凭证传给StudentRepository构造函数
  • 用户Uid:当前用户的全局唯一标识,同样传给StudentRepository构造函数
    后续所有学生数据的操作,都用传入了当前登录态参数的StudentRepository实例发起即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 05:09:31