橙宝书
端到端项目

项目四 多租户 SaaS 基础版

用共享 Worker、D1 租户边界、配额与可选客户域名完成一个最小 SaaS 闭环。

编辑与核验:橙宝书编辑团队 ·

PROJECT 04中级约 60 分钟产出:租户隔离的最小 SaaS

完成标准

用户能加入一个租户并创建记录;另一租户无法读取或修改该记录;写入受认证与配额保护;客户域名和客户代码执行被明确标记为后续能力,不阻塞基础版上线。

前置条件与运行随书示例

需要 Node.js 22、pnpm 和已安装的仓库依赖。示例位于 examples/multitenant-saas,只使用本地 D1;先复制本地 Secret、应用 migration,再写入两组演示成员关系:

cp examples/multitenant-saas/.dev.vars.example examples/multitenant-saas/.dev.vars
pnpm wrangler d1 migrations apply orange-book-saas --local --config examples/multitenant-saas/wrangler.jsonc
pnpm wrangler d1 execute orange-book-saas --local --file examples/multitenant-saas/seed.local.sql --config examples/multitenant-saas/wrangler.jsonc
pnpm wrangler dev --config examples/multitenant-saas/wrangler.jsonc

另一个终端运行 node --test examples/multitenant-saas/test/index.test.mjs,应看到 5 个测试通过。随后按示例 README 的 curl 创建项目,响应状态应为 201,JSON 中的 tenantIdtenant-a。把请求改成不存在的成员关系应得到 403,超过 PROJECT_LIMIT 应得到 409 project_quota_reached

示例认证边界

API_KEYx-user-id 只用于演示服务端成员查询。公开 SaaS 必须换成真实会话或签名身份;不能把共享 Key 放进浏览器代码。

先选择正确的多租户模型

普通 SaaS 不需要从 Workers for Platforms 开始。基础版使用一个共享 Worker 执行业务代码,在可信认证结果中取得 tenantId,并在每个 D1 查询中绑定租户条件。只有当平台需要运行客户提交或 AI 生成的不可信代码,并要求每客户独立 Worker、CPU/子请求限制和 dispatch namespace 时,再评估 Workers for Platforms。

需求起步方案升级条件
多账号共享一套业务代码Worker + D1 tenant 列数据规模或合规要求需要更强隔离
每租户子域名通配 DNS +可信 hostname 映射需要客户自有域名
客户自有域名Cloudflare for SaaS custom hostname需要自动化证书/域名生命周期
运行客户或 AI 生成代码不放进共享 WorkerWorkers for Platforms 隔离执行

最小数据边界

每张租户业务表都包含 tenant_id,唯一约束和索引也要考虑租户。认证层从会话或签名 Token 得到用户,再从成员关系得到租户;浏览器提交的 x-tenant-id 只能是选择提示,不能单独证明权限。

const { userId, tenantId } = await requireMembership(request, env);

const result = await env.DB.prepare(
  `SELECT id, name, created_at
   FROM projects
   WHERE tenant_id = ?1 AND id = ?2`
).bind(tenantId, projectId).first();

读取、更新与删除都包含 tenant_id。返回 404 比“记录存在但你无权访问”更不容易泄露其他租户对象是否存在。真实认证实现取决于项目,不要把示例里的 helper 当成已存在接口。

建立核心闭环

创建组织与成员关系

建立 tenantsusersmemberships 与一张业务表。邀请和加入操作需要一次性 token、过期时间和幂等处理。

保护每个写操作

Worker 先验证会话,再验证成员角色和 tenant 条件。写入使用 prepared statement;body、字符串、列表和上传都有明确上限。

记录业务配额

套餐配额保存在可信数据中,并在写操作前后以可审计方式更新。WAF Rate Limiting 保护网络滥用,但不能代替精确的租户计费或额度。

验证跨租户失败

创建租户 A 和 B。用 A 创建记录,再用 B 的身份读取、修改和删除,全部应返回稳定拒绝结果。日志记录 request ID 与内部原因,不回显其他租户数据。

客户域名边界

Cloudflare for SaaS 可把客户 custom hostname 接到你的平台,并管理对应证书与路由。生产就绪不能只看 CNAME:还要检查 hostname 与 SSL 状态、验证方式、fallback origin 和失败重试。若暂时只需要 tenant.example.com,先完成自有域下的子域名映射,再单独规划客户域名功能。

域名不是 tenant 身份

hostname 只用于路由到候选租户。服务端仍要把 hostname 映射到可信 tenant 记录,并对登录用户执行成员授权,不能把任意 Host header 直接当作租户 ID。

安全、成本与可观测

Secret

会话签名、第三方 API Key 与管理 Token 只放服务端 binding。

成本

按租户记录请求、D1 操作、R2 对象和后台任务的业务用量。

日志

关联 request、tenant 的内部不可逆标识、version 与错误码,避免记录敏感正文。

滥用

登录、邀请、导出和高成本 API 分别设置 Turnstile/限速与应用配额。

验证与排障

现象先检查恢复动作
401 unauthorized.dev.varsx-api-key 是否一致重新复制本地模板,不把值提交到仓库
403 forbiddenmemberships 是否同时存在 user 与 tenant只在本地执行 seed,生产通过受审计邀请流程建成员
409 project_quota_reachedPROJECT_LIMIT 与该 tenant 的记录数清理演示记录或调整可信套餐配置
D1_ERRORlocal migration 是否已应用、binding 是否名为 DB重新应用本地 migration,再重启 Wrangler

远程边界与回滚

创建 D1、应用远程 migration、配置 custom hostnames、启用 Workers for Platforms 或部署生产规则都会修改 Cloudflare 状态,本教程不自动执行。代码回滚不能删除新 Schema 或恢复已变更的客户域名。先使用兼容 migration,保留 hostname 状态表和失败重试记录;出现越权风险时优先停写并禁用相关路由,再恢复稳定代码并审计受影响租户。

下一步:建立网站与应用防御基线

官方来源

这篇内容帮你完成目标了吗?

内测反馈只在当前浏览器生成,不会自动上传。

本页目录