uniCloud多租户权限需前后端协同隔离、权限规则下沉及状态强校验,云函数须从token解析用户身份并校验所属租户,数据库操作强制带tenant_id过滤,Schema权限按租户粒度配置,前端切换租户时必须清空缓存并重载数据。

uniCloud 实现多租户权限,核心不是“加个 tenant_id 就完事”,而是前后端协同隔离 + 权限规则下沉 + 状态强校验。直接在数据库查 tenant_id 字段但没配 Schema 权限,或前端缓存了上一租户的数据,都会导致越权或脏数据。
云函数里必须做租户上下文绑定
不能依赖前端传来的 tenant_id 参数做查询过滤——它可能被篡改、缓存、或压根没传。
正确做法是在云函数入口统一提取并校验租户身份:
- 从
event.uniIdToken解析用户登录态,再查uni-id-users表拿到该用户所属的org_id或tenant_id - 若业务支持单用户跨租户(如管理员),则需额外校验用户角色是否具备访问目标租户的权限
- 所有数据库操作都显式带上
.where({ tenant_id: actualTenantId }),哪怕表结构里没这个字段也要加(防止误查)
示例片段(云函数中):
exports.main = async (event, context) => {
const { uniIdToken } = event;
const tokenInfo = await uniID.decodeToken(uniIdToken);
const userId = tokenInfo.uid;
// 查用户归属租户(假设存在 orgs_users 关联表)
const userOrgRes = await db.collection('orgs_users').where({ user_id: userId }).get();
const tenantId = userOrgRes.result.data[0]?.org_id;
if (!tenantId) throw new Error('no valid tenant for user');
// 后续所有查询都强制加 tenant_id 过滤
const list = await db.collection('approval_templates').where({ tenant_id }).get();
return list;
};
数据库 Schema 权限必须按租户粒度配置
uniCloud 的权限控制不在代码里,而在控制台的 .schema.json 文件中。即使你云函数里写了 tenant_id 过滤,如果表的读权限没放开,照样返回空或 403。
关键点:
- 进
uniCloud控制台 → 找到对应服务空间 → 数据库 → 点击表名 → “表结构” → “权限”标签页 - 读权限不能设为
"read": true(全局可读),而要写成:"read": {"auth": "user", "query": {"tenant_id": "@user.tenant_id"}} -
@user.tenant_id是前提:你得先在uni-id-users表里存了这个字段,且用户登录后 token 中能携带它(需自定义uni-id的 token payload)
否则,前端调用 uniCloud.database().collection('xxx').get() 会直接失败,连云函数都不经过。
前端切换租户时必须清空所有缓存状态
常见错误是:用户从租户 A 切到租户 B,但 ref 里的列表、分页参数、草稿数据全还是 A 的——这不是后端没隔离,是前端没重置。
具体要清理的点:
- 所有
ref或store中与租户强相关的 state(如templateList、currentPage、draftForm) - 路由参数中的
tenant_id必须参与守卫逻辑:router.beforeEach里检查当前租户是否有效,无效则跳转或清空 - 不要用
if (loaded) return这类懒加载逻辑,每次切换租户都要重新get()
尤其注意 onShow 和 onLoad 生命周期——小程序切后台再回来时,很可能没触发租户重载逻辑,得手动补。
租户标识不能只靠域名或路径硬编码
有人用 window.location.hostname.split('.')[0] 提取子域名当 tenant_id,这在 H5 可行,但在 App 或小程序里根本拿不到完整域名,直接 fallback 成空字符串或默认值。
安全做法是:
- H5:走 Nginx 子域名解析 + 前端透传给云函数(仅作辅助,不作为唯一依据)
- App / 小程序:首次登录后,后端在 token 中写入
tenant_id,前端通过uni.getStorageSync('uni_id_token')解析并缓存;后续所有请求自动带这个上下文 - 绝对避免在
main.js里一次性赋值Vue.prototype.$tenant—— 它不会随登录态变化而更新
最麻烦但最稳的方式:每次云函数调用都显式传 tenant_id,后端再和登录态比对。差一点就拒绝,而不是“尽力而为”。











