PHP SaaS平台子域名租户识别核心是HTTP层拦截Host头、提取并绑定可信租户标识,确保数据库、缓存、模型、日志等全链路强制隔离,漏任一环即导致数据越界。

PHP SaaS平台用子域名区分租户,核心不是“解析到哪”,而是“一进请求就认准是谁”。关键在 HTTP 层拦截 Host 头、提取合法子域名、绑定可信租户标识,并让后续所有环节(数据库、缓存、模型、日志)都基于这个标识做隔离。漏掉任一环,a.example.com 就可能查到 b.example.com 的数据。
子域名识别必须放在最前端中间件
不能靠 session、URL 参数或 cookie——这些在队列、定时任务、CLI 命令里根本不存在,也容易伪造。必须从服务器变量中提取:
- 优先读 $_SERVER['HTTP_X_FORWARDED_HOST'](反向代理场景,如 Nginx + PHP-FPM)
- Fallback 到 $_SERVER['HTTP_HOST'](直连时)
- 用正则白名单提取,例如:
/^([a-z0-9\-]+)\.example\.com$/i,捕获组$matches[1]是原始租户名 - 严格清洗:过滤下划线、大写字母、特殊字符,长度控制在 64 字符内,拼成安全标识(如
tenant_abc123) - 将结果绑定到请求或应用实例:
$request->tenantId = $safeId或App::instance()->tenant = $safeId - 该中间件(如
CheckTenant.php)必须排在app/middleware.php的第一位
Web 服务器要支持泛解析并透传 Host
Nginx 或 Apache 必须把所有 *.example.com 请求都转发给 PHP,且不篡改 Host 头:
- Nginx 配置示例:
server_name *.example.com example.com; - 禁用可能污染 Host 的 rewrite 规则;避免用
request()->domain()获取域名,它已被重写逻辑干扰 - HTTPS 环境必须部署通配符证书(
*.example.com),单域名证书无效 - 本地开发建议用
a.test+ 自签证书,而非a.example.com绑 hosts(现代浏览器会拦截)
数据库连接和模型查询必须强制租户隔离
只换库名不重建连接,旧连接池仍连着默认库,极易跨租户写错表:
- 在租户中间件中调用
Db::setConnectConfig('tenant', $config),其中database字段必须是完整库名(如db_tenant_abc123) - 立即执行
Db::clearConnectionPool('tenant'),清空连接缓存,确保下次Db::connect('tenant')新建连接 - 所有模型统一声明
protected $connection = 'tenant',禁止用Db::table()绕过模型 - 全局作用域(
TenantScope)必须注册在基类模型的boot()方法中,且apply()中只读取中间件注入的$request->tenantId,不读 session 或 input - 注意:count()、JOIN、软删除等场景无法被全局作用域自动覆盖,需显式加
where('tenant_id', ...)或合并作用域逻辑
缓存、Session、静态资源等全链路要加租户前缀
Redis 缓存键、Session ID、日志文件名、上传路径、模板编译目录,都必须带租户标识:
- 缓存键示例:
cache()->store('redis')->get('tenant_abc123_user_123'),不能只用user_123 - Session 配置需动态设置
session.prefix,如'session_' . $tenantId - 日志文件可按租户分目录:
runtime/log/tenant_abc123/202611/04.log - 上传路径、静态资源 URL 也要拼租户名,避免 CDN 缓存污染或跨租户访问
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











