PHP (Yii2) 接入企业级 SSO 单点登录实战教程

引言:为什么企业级应用需要 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 项目无缝融入到公司的全局统一账号生态中。
以上关于PHP (Yii2) 接入企业级 SSO 单点登录实战教程的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » PHP (Yii2) 接入企业级 SSO 单点登录实战教程
微信
支付宝