Firebase数据库规则配置:限制按邮箱匹配的读取权限
解决Firebase数据库权限问题:安全检查重复账号
你的问题核心在于客户端查询父节点/login时,父节点的读取权限未正确配置,同时直接在客户端做全局查询存在安全风险。我来帮你拆解问题并给出两种可行方案:
为什么你的原有规则无效?
Firebase的数据库规则是递归生效的:如果父节点/login的.read权限默认是false,哪怕子节点$uid的.read允许访问,客户端也无法在父节点上执行orderByChild这类需要遍历子节点的查询——因为查询操作需要先读取父节点下的所有数据进行过滤,而父节点权限直接阻止了这个行为。
你的规则1和2都只配置了子节点的读取权限,完全没处理父节点的查询场景,所以必然触发permission_denied。
方案一:用云函数处理重复检查(推荐,最安全)
直接让客户端查询整个/login节点存在数据泄露风险(哪怕规则限制,也可能被恶意利用枚举邮箱),更安全的做法是把重复检查逻辑放到云函数中,利用云函数的管理员权限完成查询,再返回结果给客户端。
步骤1:编写云函数
创建一个可调用云函数,负责检查邮箱/手机号是否已存在:
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); exports.checkDuplicateAccount = functions.https.onCall(async (data, context) => { // 验证用户是否已登录(如果是首次登录补充数据的场景) if (!context.auth) { throw new functions.https.HttpsError('unauthenticated', '请先登录'); } const { targetEmail, targetPhone } = data; let query = admin.database().ref('login'); let snapshot; // 根据传入的参数选择查询条件 if (targetEmail) { snapshot = await query.orderByChild('email_principal').equalTo(targetEmail).once('value'); } else if (targetPhone) { // 假设你存储了手机号字段`phone_principal` snapshot = await query.orderByChild('phone_principal').equalTo(targetPhone).once('value'); } else { throw new functions.https.HttpsError('invalid-argument', '必须提供邮箱或手机号'); } // 返回检查结果 if (snapshot.exists()) { const duplicateUid = Object.keys(snapshot.val())[0]; return { exists: true, uid: duplicateUid }; } else { return { exists: false }; } });
步骤2:客户端调用云函数
替换原来的查询代码,改为调用云函数:
// 初始化云函数 const checkDuplicate = firebase.functions().httpsCallable('checkDuplicateAccount'); // 调用检查逻辑 checkDuplicate({ targetEmail: newEmail }) .then(result => { const { exists, uid } = result.data; if (exists) { console.log(`该邮箱已绑定账号:${uid}`); // 处理重复账号逻辑,比如提示用户或合并账号 } else { console.log('邮箱未被使用,可以继续操作'); // 执行补充数据或账号创建逻辑 } }) .catch(error => { console.error('检查失败:', error.message); });
步骤3:收紧数据库规则
此时客户端不需要直接访问/login节点,所以可以把父节点权限锁死,只允许子节点的所有者和管理员访问:
{ "rules": { "login": { ".read": false, ".write": false, "$uid": { ".read": "auth.uid === $uid || auth.token.admin === true", ".write": "auth.uid === $uid || auth.token.admin === true" } } } }
方案二:调整数据库规则支持客户端查询(仅适合特定场景)
如果必须让客户端直接查询,你需要为父节点/login添加查询条件验证规则,确保用户只能查询特定的邮箱/手机号,而不能遍历整个节点:
{ "rules": { "login": { // 允许管理员,或已登录用户查询自己的邮箱,或查询要补充的邮箱(结合手机号验证) ".read": "auth != null && (auth.token.admin === true || (query.orderByChild === 'email_principal' && query.equalTo === auth.token.email) || (query.orderByChild === 'email_principal' && exists(root.child('login').child(auth.uid).child('phone_principal'))))", "$uid": { ".read": "auth.uid === $uid || auth.token.admin === true", ".write": "auth.uid === $uid || auth.token.admin === true" } } } }
规则说明:
- 管理员拥有完全读取权限;
- 已登录用户可以查询自己的邮箱(
auth.token.email匹配email_principal); - 手机号登录的用户(已绑定手机号)可以查询要补充的邮箱(通过
exists(root.child('login').child(auth.uid).child('phone_principal'))验证用户身份)。
⚠️ 注意:这种方式仍存在一定风险,比如恶意用户可能通过频繁调用查询枚举邮箱,所以还是推荐方案一。
内容的提问来源于stack exchange,提问作者Leandro Furini
相关产品推荐
相关产品推荐

