跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

ThinkPHP如何实现多域名单点登录 ThinkPHP跨域认证与域名绑定实现

ThinkPHP多域名共享session需显式配置session.cookie_domain为'.example.com',否则浏览器cookie按域名隔离;token方案须将域名纳入缓存key;CORS需指定Origin且允许credentials;所有子域必须共用同一session.name。 ThinkPHP 的 session 域名绑定必须显式设置
session.cookie_domain
默认情况下,ThinkPHP 不会自动适配多域名共享 session,哪怕你用了同一个 Redis 或数据库存 session,浏览器端的 cookie 仍按当前域名隔离。关键在 PHP 底层的 cookie 域名策略,不是框架能绕过的。 实操上,得在
config/session.php
里手动配置:
return [ 'type' => 'redis', 'cookie_domain' => '.example.com', // 注意开头的点,表示匹配所有子域 ];
常见错误现象:
login.example.com
登录后,在
admin.example.com
拿不到
session_id
,
$_SESSION
为空 —— 八成是漏了这个配置,或写成了
example.com
(没点)导致子域不生效。 注意点: 立即学习 “ PHP免费学习笔记(深入) ”;
cookie_domain
必须以点开头(如
.example.com
),否则只匹配精确域名 不能跨主域(比如
.example.com
和
.other.com
无法共享) 本地开发时用
localhost
无法设 domain,得切到真实二级域名(如
dev.example.com
)测试 Token 认证场景下,
think\facade\Cache
存 token 要带 domain 前缀 如果不用 session,而是用 token(如 JWT 或自定义字符串)+ 缓存做登录态,那“多域名”意味着不同域名请求要能查到同一份 token 数据。但直接存
$cache->set('token_'.$uid, ...)
会导致冲突 —— 同一用户在
a.example.com
和
b.example.com
登录,后一次会覆盖前一次。 解决方案是把域名信息作为 key 的一部分:
$domain = request()->host(); $cacheKey = 'token_' . $uid . '_' . md5($domain); $cache->set($cacheKey, $tokenData, 3600);
但更稳妥的做法是:统一用主域标识,比如从
request()->host()
提取根域: 用
str_replace('www.', '', $host)
粗略处理 或借助
parse_url()
+ 白名单判断(如只允许
example.com
及其子域) 避免前端伪造 host 头,服务端要做
in_array($domain, ['example.com', 'a.example.com', 'b.example.com'])
校验 跨域请求时,
CORS
配置和
withCredentials
必须同时生效 前端发请求(比如 Vue 项目在
fe.example.com
)调用
api.example.com
接口登录,若没正确配 CORS,浏览器会直接拦截响应,连 session cookie 都不会带过去。 ThinkPHP 6+ 的中间件里要确保: 响应头包含
Access-Control-Allow-Origin: https://fe.example.com
(不能是
*
,否则
withCredentials
失效) 包含
Access-Control-Allow-Credentials: true
预检请求(OPTIONS)也要返回这些头,且不能缓存(加
Access-Control-Max-Age: 0
更稳) 容易踩的坑:
Access-Control-Allow-Origin
写成
*
后,前端 JS 里还写了
credentials: 'include'
,结果 fetch 拿不到 cookie —— 浏览器直接静默拒绝,控制台可能只报 “CORS error”,不提示具体原因。 Redis 存 session 时,
session.name
不能各域名用不同值 有人为区分域名,给每个子域单独配一个
session.name
(比如
PHPSESSID_A
、
PHPSESSID_B
),这反而破坏单点登录:登录态被拆成多个独立 session,无法互通。 正确做法是所有域名共用一套 session 配置,包括:
session.name
统一(如默认
PHPSESSID
)
session.save_handler
一致(都用 redis)
session.redis.host
和
port
指向同一个实例 性能影响很小,但一旦 name 不一致,框架底层就认为是两个 session 实例,
session_start()
启动的是完全隔离的存储空间 —— 这个细节很多人在调试时忽略,反复清 cookie 也没用。 复杂点在于:上线后不同团队维护不同子域,各自改配置却没同步,结果生产环境部分域名登不上。这种问题往往要翻日志比对
session_id()
输出才看得出来。

相关文章