php实现单点登录必须解决三个前提:共享认证凭证(通过idp签发jwt)、可信身份断言传递(后端验签+校验iss/exp)、统一登出协调(idp回调sp登出接口清会话)。

PHP 实现单点登录必须解决的三个前提
单点登录(SSO)不是靠一个 session_start() 就能跨域打通的。PHP 本身不处理跨域认证,它只是执行端——真正起作用的是「共享认证凭证」+「可信身份断言传递」+「统一登出协调」。没这三块,任何“SSO”都是伪方案。
常见错误现象:$_SESSION 在 A 域能读、B 域为空;用 setcookie() 写了同名 Cookie 却被浏览器拒绝(因 domain 不匹配或 SameSite 阻断);用户在 A 站登出,B 站仍能访问受保护资源。
- 必须有一个独立的身份提供方(IdP),比如自建的
auth.example.com,所有应用(SP)只跟它通信,不互相信任 - PHP 应用不能直接读写其他域名的 Cookie 或 Session,必须通过重定向 + Token(如 JWT)或后端代理方式交换身份声明
- Session 生命周期要和 IdP 的 Token 有效期对齐,不能依赖 PHP 默认的
session.gc_maxlifetime独立管理
用 JWT + OAuth2 实现轻量级跨域 SSO(推荐落地路径)
比起 SAML,JWT + OAuth2 更适合 PHP 中小项目:标准库支持好(firebase/php-jwt)、调试直观、前端也容易配合。关键不是“怎么生成 Token”,而是“谁签发、谁验证、怎么传”。
使用场景:多个子域(app1.example.com、app2.example.com)共用同一套登录页和用户中心。
立即学习“PHP免费学习笔记(深入)”;
- IdP 登录成功后,用私钥签发 JWT,payload 至少含
sub(用户唯一标识)、exp、iss(如https://auth.example.com) - SP(你的 PHP 应用)收到前端传来的
Authorization: Bearer <token></token>后,用 IdP 公钥验签,再检查iss和exp,**不解析就信任** - 避免把 JWT 存在前端 localStorage —— 改用 httpOnly Cookie(
SameSite=Lax,Domain=.example.com),由 PHP 后端透传给 IdP 验证接口 - 注意
openssl_pkey_get_public()加载公钥失败时不会报错,要用openssl_error_string()主动捕获
PHP 中处理跨域登出同步的坑
登出不是删掉自己 $_SESSION 就完事。用户在 app1.example.com 点登出,app2.example.com 的会话还在,这是最常被忽略的环节。
错误做法:前端跳转到 https://auth.example.com/logout 后就认为结束;结果 IdP 清了主会话,但各 SP 没通知到,session_destroy() 根本没触发。
- IdP 登出时,必须记录当前在线 SP 列表(比如从上次登录时的
redirect_uri域名提取并缓存) - PHP 应用需暴露一个后端登出回调接口(如
/sso-logout),IdP 用curlPOST 调用,带一次性logout_token(JWT 格式,含jti防重放) - 这个接口里必须调用
session_unset()+session_destroy()+setcookie()清空本地会话 Cookie,且Domain参数要和登录时一致(如.example.com) - 别依赖前端 js 发起登出请求——网络失败或用户关闭页面会导致同步中断
为什么不要自己实现 Cookie 共享或 session_id 透传
看到 “PHP 跨域 SSO” 就想改 session.cookie_domain 或拼接 PHPSESSID 传参?这基本等于放弃安全边界。
典型错误现象:session_start() 报 Failed to decode session Object;不同子域间 session_id() 相同但数据读不到;csrf token 失效。
-
session.cookie_domain = ".example.com"只解决同根域名下的 Cookie 共享,无法解决跨协议(http/https)、跨端口、或完全不同的域名(如admin.net和user.org) - 手动传
PHPSESSIDvia URL 或 POST 是严重反模式:Token 泄露风险高,且现代浏览器对第三方 Cookie 限制越来越严(SameSite=Strict默认生效) - PHP 的
session_set_save_handler()自定义存储看似灵活,但一旦 IdP 重启或密钥轮换,所有 SP 的 session 数据就不可解,维护成本爆炸
复杂点在于:SSO 不是功能模块,是架构约定。每个 PHP 应用都得按同一套 Token 结构、错误码、超时逻辑来写验证逻辑。最容易被忽略的,是 IdP 和各 SP 之间时钟不同步导致的 exp 校验失败——别忘了加个 1~2 分钟的 leeway。