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
相关产品推荐
相关产品推荐

