PHP项目如何实现单点登录?完整实现指南与最佳实践
目录导读
- 什么是单点登录(SSO)?
- PHP实现SSO的常见方案对比
- 基于令牌(Token)的SSO核心逻辑详解
- 实战:利用OAuth 2.0协议构建SSO系统
- 安全注意事项与常见陷阱
- 问答环节:SSO实现中的高频问题
什么是单点登录(SSO)?
单点登录(Single Sign-On,简称SSO)是一种身份验证机制,允许用户使用一组凭据(如用户名和密码)登录一次,即可访问多个相互独立的系统或应用程序,登录Google账号后,你可以无缝使用Gmail、Google Drive、YouTube等服务。

在PHP项目中实现SSO,本质上是将认证逻辑从各个子系统中剥离,集中到一个独立的认证中心(Auth Server),子服务不再维护自己的用户密码数据库,而是信任认证中心颁发的身份凭证。
PHP实现SSO的常见方案对比
| 方案 | 适用场景 | 复杂度 | 安全性 |
|---|---|---|---|
| 共享Cookie+同域名 | 所有服务在同一主域名下 | ||
| OAuth 2.0/OpenID Connect | 跨域名、第三方集成 | ||
| JWT(JSON Web Token) | 无状态、API服务 | ||
| SAML(安全断言标记语言) | 企业级、大型系统 |
对于大多数PHP项目(特别是中小型企业系统),JWT + 认证中心 方案是性价比最高的选择,它既避免了SAML的繁重配置,又能很好地处理跨域问题。
基于令牌(Token)的SSO核心逻辑详解
核心组件
- Auth Server(认证中心):专门处理用户登录/登出,签发令牌
- Resource Server(业务系统):通过验证令牌来授权访问
- Token(访问令牌):通常是JWT格式,包含用户ID、角色、过期时间等信息
工作流程
用户首次访问App A,未登录 → 重定向到Auth Server登录页
2. 用户在Auth Server输入凭据,Auth Server验证成功后:
- 创建全局会话(Session)
- 生成JWT令牌,写入Cookie或返回给前端
- 附加返回URL参数,重定向回App A
3. App A收到令牌,携带令牌向Auth Server验证有效性
4. Auth Server返回用户信息(如user_id),App A创建本地会话
5. 用户再访问App B时:
- 检查到本地无会话 → 重定向到Auth Server
- Auth Server发现已有全局会话(Cookie中有JWT)
- 直接签发新令牌,跳转回App B,完成自动登录
关键代码片段(PHP)
Auth Server签发JWT:
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
function issueJWT($userId, $secretKey) {
$payload = [
'iss' => 'auth.example.com', // 签发者
'iat' => time(), // 签发时间
'exp' => time() + 3600, // 1小时后过期
'user_id' => $userId,
'roles' => ['admin', 'editor']
];
return JWT::encode($payload, $secretKey, 'HS256');
}
业务系统验证JWT:
function verifyJWT($jwt, $secretKey) {
try {
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
return (array) $decoded;
} catch (\Exception $e) {
return null; // 令牌无效或过期
}
}
实战:利用OAuth 2.0协议构建SSO系统
OAuth 2.0是目前最标准的授权协议,推荐使用 thephpleague/oauth2-server 或 firebase/php-jwt 结合自定义逻辑。
步骤1:数据库设计(认证中心)
CREATE TABLE oauth_clients (
client_id VARCHAR(80) PRIMARY KEY,
client_secret VARCHAR(80) NOT NULL,
redirect_uri VARCHAR(2000) NOT NULL
);
CREATE TABLE oauth_access_tokens (
access_token VARCHAR(80) PRIMARY KEY,
client_id VARCHAR(80) NOT NULL,
user_id VARCHAR(80) NOT NULL,
expires TIMESTAMP NOT NULL
);
步骤2:用户登录接口(Auth Server)
// 路由:GET /login
// 负责显示登录表单,POST后验证凭据并生成授权码
public function handleLogin(Request $request) {
$username = $request->input('username');
$password = $request->input('password');
// 验证用户(这里简化,实际应使用hash验证)
$user = UserModel::findByUsername($username);
if (!$user || !password_verify($password, $user->password)) {
return redirect()->back()->withErrors('账号或密码错误');
}
// 生成授权码(授权码模式)
$authCode = bin2hex(random_bytes(16));
// 存储授权码到数据库或缓存,关联用户ID和客户端ID
Cache::put('auth_code:' . $authCode, [
'user_id' => $user->id,
'client_id' => $request->input('client_id'),
'expires_at' => now()->addMinutes(10)
], 600);
// 重定向回到业务系统
$redirectUri = $request->input('redirect_uri') . '?code=' . $authCode;
return redirect($redirectUri);
}
步骤3:业务系统获取令牌
// 业务系统收到授权码后,向Auth Server请求access_token
$response = Http::asForm()->post('https://auth.example.com/token', [
'grant_type' => 'authorization_code',
'code' => $code,
'client_id' => 'your_client_id',
'client_secret' => 'your_client_secret',
'redirect_uri' => 'https://app-a.example.com/callback'
]);
$tokenData = $response->json();
// 将access_token存储到会话中
session(['access_token' => $tokenData['access_token']]);
步骤4:跨域Cookie问题解决方案
由于SSO通常涉及不同域名,主流的解决方法是:
- 使用Auth Server域名下的Cookie存储全局会话
- 业务系统通过iframe + postMessage或HTTP重定向传递令牌
推荐使用 OpenID Connect 的 id_token 进行跨域身份传播,或者将JWT放在URL查询参数中(需注意HTTPS)。
安全注意事项与常见陷阱
- 永远使用HTTPS:令牌在传输过程中必须加密
- 设置合理的令牌过期时间:建议access_token 15分钟-1小时,refresh_token有效期稍长
- 防止CSRF攻击:在认证流程中加入state参数
- 避免令牌泄露:不要在URL中直接传递JWT,改用POST或Authorization Header
- 服务端验证:不要在客户端(前端)解析JWT来做权限判断
- 登出逻辑:必须同时清除Auth Server的全局会话和所有业务系统的本地会话
问答环节:SSO实现中的高频问题
Q1:PHP实现SSO一定要用OAuth 2.0吗? A:不是必须,如果所有子系统在同一域名下,使用共享Cookie + Session池即可实现,但对于跨域名或前后端分离的项目,OAuth 2.0是最规范的选择。
Q2:如何解决JWT泄露后的安全问题? A:采用短期令牌(如15分钟过期)配合刷新令牌(refresh token),同时实现令牌黑名单机制(例如存储已吊销的JWT ID到Redis)。
Q3:SSO系统的Session存储该用什么? A:Auth Server建议使用Redis或数据库存储全局会话,业务系统建议使用本地Redis存储,避免使用文件Session,因为分布式环境下会出问题。
Q4:多个子系统如何共享用户权限数据? A:方法一:权限编码写入JWT的claim中(适合权限较少且稳定的系统),方法二:业务系统根据JWT中的user_id,调用Auth Server的API获取实时权限(推荐)。
Q5:有没有开源的PHP SSO库推荐? A:推荐以下组合:
- 认证服务器:onelogin/php-saml(企业级SAML)
- OAuth服务器:thephpleague/oauth2-server(推荐)
- JWT处理:firebase/php-jwt
- 集成解决方案:laravel/passport(Laravel专用)
通过以上步骤,你可以在PHP项目中构建出一个健壮的单点登录系统,关键是将认证逻辑集中管理,并确保令牌的生成、传递和验证过程严格遵循安全规范,对于中小企业或内部系统,建议先从JWT + 简单认证中心入手,再逐步演进到OAuth 2.0标准协议。