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

Firebase如何实现服务端校验后修改数据库,不开放用户直接写权限

Firebase 许可证激活服务端校验方案

你要实现的逻辑核心是把所有敏感操作收拢到Firebase可信服务端环境执行,全程不给客户端开放敏感数据的写权限,具体落地步骤如下:

核心流程闭环

  • 客户端仅负责收集用户输入的许可证密钥,携带当前登录用户身份发起请求,不执行任何敏感写操作
  • 所有许可证有效性校验、数据更新逻辑全部在服务端可信环境完成
  • 数据库安全规则从根源锁死客户端对许可证表、用户订阅字段的写权限,从规则层避免越权

第一步:配置最小权限的数据库安全规则

先从规则层堵死越权可能,以Firestore为例,安全规则配置如下:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // 许可证存储集合:所有客户端完全禁止读写,仅服务端管理员账号可访问
    match /licenses/{licenseId} {
      allow read, write: if false;
    }
    // 用户信息集合:用户仅能读取自身文档,无订阅相关字段的写权限
    match /users/{userId} {
      allow read: if request.auth != null && request.auth.uid == userId;
      // 仅允许用户更新昵称、头像这类非敏感字段,订阅相关字段完全禁写
      allow write: if request.auth != null && request.auth.uid == userId 
        && !request.resource.data.keys().hasAny(["subscriptionTier", "subscriptionExpireAt", "activatedLicense"]);
    }
  }
}

你需要提前在licenses集合中录入所有合法许可证,每个许可证文档包含字段:密钥哈希值、是否有效、绑定用户ID(未激活为空)、到期时间,这个集合客户端完全无法访问,不会出现密钥泄露、越权篡改的问题。


第二步:部署服务端可信接口

最省事的方案是使用Firebase Callable Cloud Functions,不需要自己搭服务器、配鉴权逻辑,平台自动校验请求方的登录身份,最小可用的实现代码如下(Node.js环境):

const functions = require('firebase-functions/v1');
const admin = require('firebase-admin');
const crypto = require('crypto');
admin.initializeApp();
const db = admin.firestore();

// 许可证激活接口
exports.activateLicense = functions.https.onCall(async (data, context) => {
  // 校验登录态,未登录直接拒绝
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', '请先登录后再操作');
  }
  const userId = context.auth.uid;
  const inputKey = data.licenseKey?.trim();
  if (!inputKey) {
    throw new functions.https.HttpsError('invalid-argument', '请输入有效的许可证密钥');
  }

  // 服务端查询许可证信息做校验,全程逻辑不暴露给客户端
  const keyHash = crypto.createHash('sha256').update(inputKey).digest('hex');
  const licenseSnapshot = await db.collection('licenses')
    .where('keyHash', '==', keyHash)
    .limit(1)
    .get();
  if (licenseSnapshot.empty) {
    throw new functions.https.HttpsError('not-found', '许可证密钥无效');
  }
  const licenseDoc = licenseSnapshot.docs[0];
  const licenseData = licenseDoc.data();

  // 多维度校验许可证状态
  if (!licenseData.isValid) {
    throw new functions.https.HttpsError('failed-precondition', '许可证已被禁用');
  }
  if (licenseData.activatedUid && licenseData.activatedUid !== userId) {
    throw new functions.https.HttpsError('already-exists', '该许可证已绑定其他账号');
  }
  if (licenseData.expireAt && licenseData.expireAt.toDate() < new Date()) {
    throw new functions.https.HttpsError('deadline-exceeded', '许可证已过期');
  }

  // 校验通过,用服务端管理员权限原子更新两份数据,避免状态不一致
  const batch = db.batch();
  // 更新许可证绑定信息
  batch.update(licenseDoc.ref, {
    activatedUid: userId,
    activatedAt: admin.firestore.FieldValue.serverTimestamp()
  });
  // 更新用户订阅状态为高级版
  batch.update(db.collection('users').doc(userId), {
    subscriptionTier: 'premium',
    activatedLicenseKeyHash: keyHash,
    subscriptionExpireAt: licenseData.expireAt,
    updatedAt: admin.firestore.FieldValue.serverTimestamp()
  });
  await batch.commit();

  // 返回成功结果给客户端
  return {
    success: true,
    subscriptionTier: 'premium',
    expireAt: licenseData.expireAt
  }
});

如果你习惯自己维护后端服务,只需要在自己的服务端集成Firebase Admin SDK,逻辑和上面完全一致即可,注意绝对不要把Admin SDK的服务账号密钥泄露到客户端。


第三步:客户端对接逻辑

客户端不需要任何数据库写操作,逻辑非常简单:

  • 用户提交许可证时,直接调用上面部署的Callable云函数,传入用户输入的密钥
  • 收到接口返回的成功响应后,重新拉取一次当前用户的信息文档,确认订阅状态更新后,给用户展示激活成功的提示和高级权益
  • 如果接口返回错误,直接把对应错误提示展示给用户即可

可选加固项

  • 给激活接口加频率限制:单个用户1分钟内最多调用3次,防止暴力撞库猜密钥
  • 配置定时任务:每天自动扫描到期的高级订阅,把对应用户的订阅状态改回标准档
  • 加操作日志:所有激活、订阅变更操作都存在单独的日志集合,方便后续排查问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:45:37