文章总结: 本文详细讲解PHP(Yii2)框架接入企业级SSO单点登录的完整方案。核心内容包括SSO的Ticket认证机制、三层架构实现(接口定义-服务实现-控制器调用),以及企业级安全防护要点。文章提供了完整的代码示例,涵盖认证流程、用户信息同步和会话管理,并强调Ticket防重放、用户状态同步、全链路加密等安全准则。 综合评分: 82 文章分类: 应用安全,安全开发,解决方案,WEB安全,安全建设
别再各写各的登录了!三步吃透 PHP (Yii2) 接入企业级 SSO 单点登录
原创
学习与分享 学习与分享
php的自我修养
2026年6月29日 08:30 广东
在小说阅读器读本章
去阅读
商务合作加微信:2230304070
学习与交流:PHP技术自我修养微信群
免费领取phpstorm正版激活码
获取地址:https://web.52shizhan.cn
以下文章正文
research and development
引言:为什么企业级应用需要 SSO?
在企业级应用群落中,孤立的账号体系是研发和用户的噩梦。当公司内部孵化出 OA、CRM、ERP 等多套系统时,如果用户在每个系统都需要重复输入账号密码,不仅体验极差,数据孤立、权限混乱、安全防线难以收拢等痛点也会接踵而至。
SSO(Single Sign-On,单点登录) 正是解决这一痛点的终极方案。它允许用户在多个互信的独立业务系统中,“一次登录,到处通行”。本文将带你从底层机制出发,用 PHP (Yii2 架构) 闭环实现一套工业级的 SSO 接入方案。
一、 核心复盘:SSO 认证究竟是如何运转的?
要接入 SSO,首先必须搞懂它的核心账密置换凭证 —— Ticket(授权码/票据)机制。
我们以经典的中心化认证流为例,当用户访问受保护的业务系统时,其核心步骤如下:
-
路由拦截
:用户访问业务系统 B。业务系统发现用户未登录,通过 302 重定向将用户引导至 SSO 认证中心。
-
凭证分发
:用户在 SSO 中心完成账号密码认证。SSO 中心验证通过后,生成一个短期内有效的随机凭证 Ticket,并带着这个 Ticket 重定向回业务系统 B 预留的回调地址。
-
后台置换
:业务系统 B 拿到 Ticket 后,在后端(Server to Server)发起请求,向 SSO 中心验证 Ticket 的合法性,并换取当前用户的核心信息(如工号、邮箱、角色)。
-
本地授信
:业务系统 B 验证无误,将用户信息写入本地 Session/Token,自动完成本地登录,页面正常响应。
二、 实战演练:三步打通 PHP 的 SSO 接入
为了让代码更具规范性和扩展性,我们采用 接口定义 服务实现 控制器调用 的三层架构。
步骤 1:定义统一的 SSO 契约接口
首先,我们定义一个规范的接口。不论未来的 SSO 协议是基于标准的 OIDC、OAuth2 还是自研的 CAS,业务层只需要关心这两个核心动作。
<?php
namespace app\components\sso;
interface SSOLoginInterface
{
/**
* 获取单点登录中心授权跳转地址
* @return string 跳转到SSO中心的完整URL
*/
public function getAuthUrl(): string;
/**
* 通过回调获得的 Ticket 换取 SSO 中心的用户信息
* @param string $ticket 认证票据
* @return array 用户基础信息数组
* @throws \Exception 校验失败时抛出异常
*/
public function getInfoFromTicket(string $ticket): array;
}
步骤 2:落地具体对接的 SSO 服务类
这里我们模拟一个具体的 CompanySSOService。在实际生产环境下,你可以将这里的 curl 请求替换为你们公司的 SSO 中心真实 API。
<?php
namespaceapp\components\sso;
useYii;
useyii\base\Component;
class CompanySSOService extends Component implements SSOLoginInterface
{
public $ssoHost = 'https://sso.company.com';
public $clientId = 'crm_system_01';
public $clientSecret = 'sso_secret_key_xxxxxx';
publicfunction getAuthUrl(): string
{
// 告诉SSO中心,认证成功后跳回本地的什么地方
$callbackUrl = urlencode(Yii::$app->request->getHostInfo() . '/sso/callback');
return"{$this->ssoHost}/login?client_id={$this->clientId}&redirect_uri={$callbackUrl}";
}
publicfunction getInfoFromTicket(string $ticket): array
{
$apiExhangeUrl = "{$this->ssoHost}/api/verify-ticket";
// 发起后端安全的 Server to Server 请求,杜绝前端伪造
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiExhangeUrl);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'ticket' => $ticket,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
]));
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
if (empty($result) || !isset($result['success']) || !$result['success']) {
thrownew \Exception("SSO凭证校验失败: " . ($result['message'] ?? '未知错误'));
}
return $result['data']; // 返回格式统一包含: username, email, truename
}
}
步骤 3:编写控制器,闭环回调登录逻辑
在 Yii2 框架中创建 SsoController。它是整个登录闭环的控制中枢,负责处理Ticket 的接收、新用户的自动常驻同步、以及本地会话的建立。
<?php
namespaceapp\controllers;
useYii;
useyii\web\Controller;
useyii\web\BadRequestHttpException;
useapp\models\User;
useapp\components\sso\CompanySSOService;
class SsoController extends Controller
{
/**
* @return CompanySSOService
*/
privatefunction getSsoService()
{
// 建议在 Yii 的 components 中配置注入,此处快速实例化演示
returnnew CompanySSOService();
}
/**
* 登录入口触发表
*/
publicfunction actionLogin()
{
// 如果本地已经有登录态,直接去后台首页
if (!Yii::$app->user->isGuest) {
return$this->redirect(['/dashboard']);
}
// 渲染登录引导页,将前端登录按钮的链接指向 SSO 授权页
return$this->render('login', [
'ssoUrl' => $this->getSsoService()->getAuthUrl()
]);
}
/**
* SSO 中心认证通过后的核心回调接口
*/
publicfunction actionCallback()
{
$ticket = Yii::$app->request->get('ticket');
if (empty($ticket)) {
thrownew BadRequestHttpException('授权失败:缺失关键 ticket 参数');
}
try {
// 1. 去 SSO 中心洗白,拿回用户的真实资料
$ssoUser = $this->getSsoService()->getInfoFromTicket($ticket);
if (empty($ssoUser['username'])) {
return$this->renderError('SSO 授信中心返回的用户数据不完整');
}
// 2. 统一格式规整(防止大小写引发的账号双胞胎问题)
$username = strtolower(trim($ssoUser['username']));
// 3. 检索本地账号仓库,不存在则自动激活“影子上游账号”
$user = User::find()->where(['username' => $username])->one();
if (!$user) {
$user = new User();
$user->username = $username;
$user->email = $ssoUser['email'] ?? "{$username}@company.com";
$user->realname = $ssoUser['truename'] ?? '新员工';
$user->role = User::ROLE_MEMBER; // 默认赋予普通成员权限
$user->status = User::STATUS_ACTIVE;
if (!$user->save()) {
thrownew \Exception("本地同步创建用户失败:" . json_encode($user->getErrors()));
}
}
// 4. 正式签发本地登录会话(Session/Cookie 保持 30 天)
Yii::$app->user->login($user, 3600 * 24 * 30);
// 5. 顺滑流转至系统内部首页
return$this->redirect(['/dashboard']);
} catch (\Exception $e) {
Yii::error("SSO登录异常挂起: " . $e->getMessage(), __METHOD__);
return$this->renderError($e->getMessage());
}
}
privatefunction renderError($msg)
{
return$this->render('error', [
'message' => $msg,
'retryUrl' => $this->getSsoService()->getAuthUrl()
]);
}
}
三、 架构师避坑:企业级 SSO 的三大防御工事
单点登录一旦被攻破,意味着黑客拿到了进入公司所有系统的万能钥匙。在生产环境中部署时,必须牢记以下三大安全增强准则:
1. Ticket 防重放与防篡改
-
一次性失效
:Ticket 在设计时,必须在 SSO 服务端执行 “阅后即焚” 策略。即任何一个 Ticket 只要被业务系统验证过一次,无论成功与否,在 Redis 中都必须立刻被判定为作废。
-
时效极短化
:Ticket 只是用于置换 Token 的临时媒介,有效期通常应该限制在 30 秒 ~ 1 分钟 之内。
2. 用户动态映射与状态同步
-
唯一标识锚定
:在多系统间匹配用户时,不要直接依赖极易被修改的手机号或用户名。推荐使用全局唯一的员工工号(Staff ID)或不可变的专属 UID 作为主键映射绑定。
-
状态熔断
:如果员工在 SSO 中心被 HR 标记为离职或禁用,当其再次访问业务系统时,业务系统如果直接读取本地 Session,会导致离职员工仍有系统权限。因此,本地 Session 存活期不宜过长,或者业务系统在核心节点需定期(如每 5 分钟)异步校验一次 SSO 态的有效性。
3. 全链路安全防御
-
加签防伪造
:SSO 中心和各业务系统之间,推荐使用基于 RSA 签名 或 JWT 非对称加密 的算法。即使 Ticket 被拦截,黑客在没有私钥的情况下也无法伪造用户信息。
-
全站 HTTPS
:由于全链路都在重定向传输敏感凭证,严禁在 HTTP 纯明文环境下裸奔,所有域名必须无条件强制开启 HTTPS。
四、 总结
通过上述的逻辑重构与代码补充,我们可以发现,PHP 在接入 SSO 时拥有天然的高效和灵活性。只要我们卡死回调接口,做好 Ticket 的后台二次验证和账号的动态同步,就可以快速让任何一个新旧 PHP 项目无缝融入到公司的全局统一账号生态中。
原文链接:
以上就是本篇文章的全部内容,希望各位程序员们努力提升个人技术。最后,小编温馨提示:每天阅读5分钟,每天学习一点点,每天进步一点点。
点个赞
再走吧
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:php的自我修养 学习与分享 学习与分享《别再各写各的登录了!三步吃透 PHP (Yii2) 接入企业级 SSO 单点登录》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论