本文目录导读:

我将为您提供一个完整的PHP单点登录(SSO)集成方案,包括核心组件、实现步骤和示例代码。
SSO系统架构
核心组件
├── sso-server/ # SSO认证中心
├── client-app1/ # 客户端应用1
├── client-app2/ # 客户端应用2
└── common/ # 共享库
认证流程
graph LR
A[用户访问应用1] --> B[检查会话]
B -->|无会话| C[跳转SSO服务器]
C --> D[用户登录]
D --> E[生成令牌]
E --> F[回调应用1]
F --> G[建立本地会话]
G --> H[访问应用2]
H --> I[检查会话]
I -->|无会话| C
SSO服务器实现
数据库表结构
-- 用户表 CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(255) NOT NULL, `email` varchar(100) DEFAULT NULL, `status` tinyint(1) NOT NULL DEFAULT '1', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB; -- 令牌表 CREATE TABLE `tokens` ( `id` int(11) NOT NULL AUTO_INCREMENT, `token` varchar(255) NOT NULL, `user_id` int(11) NOT NULL, `expires_at` datetime NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `user_id` (`user_id`), KEY `token` (`token`), CONSTRAINT `tokens_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ) ENGINE=InnoDB; -- 应用表 CREATE TABLE `applications` ( `id` int(11) NOT NULL AUTO_INCREMENT, `app_name` varchar(100) NOT NULL, `app_key` varchar(255) NOT NULL, `app_secret` varchar(255) NOT NULL, `callback_url` varchar(255) NOT NULL, `status` tinyint(1) NOT NULL DEFAULT '1', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB;
SSO服务器核心代码
<?php
// sso-server/index.php
session_start();
require_once 'config.php';
require_once 'db.php';
require_once 'TokenManager.php';
class SSOServer {
private $db;
private $tokenManager;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
$this->tokenManager = new TokenManager();
}
// 处理登录请求
public function handleLogin() {
$appKey = $_GET['app_key'] ?? '';
$callbackUrl = $_GET['callback_url'] ?? '';
// 验证应用
if (!$this->validateApp($appKey, $callbackUrl)) {
$this->jsonResponse(403, 'Invalid application');
}
// 检查是否已登录SSO
if ($this->isLoggedIn()) {
$this->redirectWithToken($callbackUrl);
}
// 显示登录页面
$this->showLoginForm($appKey, $callbackUrl);
}
// 处理登录提交
public function handleLoginSubmit() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->jsonResponse(405, 'Method not allowed');
}
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$appKey = $_POST['app_key'] ?? '';
$callbackUrl = $_POST['callback_url'] ?? '';
// 验证用户
$user = $this->authenticate($username, $password);
if (!$user) {
$this->jsonResponse(401, 'Invalid credentials');
}
// 创建SSO会话
$this->createSSOSession($user['id']);
// 生成一次性令牌
$token = $this->tokenManager->generateToken($user['id']);
// 重定向回客户端
$this->redirectWithToken($callbackUrl, $token);
}
// 验证令牌(客户端应用调用)
public function validateToken() {
$token = $_POST['token'] ?? '';
$appKey = $_POST['app_key'] ?? '';
$appSecret = $_POST['app_secret'] ?? '';
// 验证应用
if (!$this->validateAppCredentials($appKey, $appSecret)) {
$this->jsonResponse(403, 'Invalid application credentials');
}
// 验证令牌
$userData = $this->tokenManager->validateToken($token);
if (!$userData) {
$this->jsonResponse(401, 'Invalid or expired token');
}
$this->jsonResponse(200, 'Token valid', ['user' => $userData]);
}
// 处理注销
public function handleLogout() {
$token = $_GET['token'] ?? '';
if ($token) {
// 销毁SSO会话
$this->destroySSOSession($token);
}
// 重定向到登录页面
header('Location: login.php?logout=1');
exit;
}
private function authenticate($username, $password) {
$sql = "SELECT * FROM users WHERE username = ? AND status = 1";
$stmt = $this->db->prepare($sql);
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
return $user;
}
return false;
}
private function createSSOSession($userId) {
$_SESSION['sso_user_id'] = $userId;
$_SESSION['sso_login_time'] = time();
}
private function validateApp($appKey, $callbackUrl) {
$sql = "SELECT * FROM applications WHERE app_key = ? AND status = 1";
$stmt = $this->db->prepare($sql);
$stmt->execute([$appKey]);
$app = $stmt->fetch(PDO::FETCH_ASSOC);
return $app && $app['callback_url'] === $callbackUrl;
}
private function validateAppCredentials($appKey, $appSecret) {
$sql = "SELECT * FROM applications WHERE app_key = ? AND app_secret = ? AND status = 1";
$stmt = $this->db->prepare($sql);
$stmt->execute([$appKey, $appSecret]);
return $stmt->fetch(PDO::FETCH_ASSOC) !== false;
}
private function redirectWithToken($callbackUrl, $token = null) {
$separator = strpos($callbackUrl, '?') ? '&' : '?';
if ($token) {
// 临时令牌,客户端用于换取用户信息
$data = [
'token' => $token,
'expires' => time() + 300 // 5分钟有效期
];
$encoded = base64_encode(json_encode($data));
$redirectUrl = $callbackUrl . $separator . 'sso_data=' . urlencode($encoded);
} else {
// 用户已登录,生成新令牌
$userId = $_SESSION['sso_user_id'];
$token = $this->tokenManager->generateToken($userId);
$redirectUrl = $callbackUrl . $separator . 'sso_token=' . $token;
}
header('Location: ' . $redirectUrl);
exit;
}
private function isLoggedIn() {
return isset($_SESSION['sso_user_id']) && $_SESSION['sso_user_id'] > 0;
}
private function showLoginForm($appKey, $callbackUrl) {
?>
<!DOCTYPE html>
<html>
<head>
<title>SSO Login</title>
<style>
body { font-family: Arial, sans-serif; max-width: 400px; margin: 50px auto; }
.container { padding: 30px; border: 1px solid #ddd; border-radius: 5px; }
input { display: block; width: 100%; margin: 10px 0; padding: 8px; }
button { background: #007bff; color: white; border: none; padding: 10px 20px; cursor: pointer; }
.error { color: red; }
</style>
</head>
<body>
<div class="container">
<h2>统一身份认证</h2>
<?php if (isset($_GET['error'])): ?>
<p class="error">用户名或密码错误</p>
<?php endif; ?>
<form method="POST" action="login.php">
<input type="hidden" name="app_key" value="<?php echo htmlspecialchars($appKey); ?>">
<input type="hidden" name="callback_url" value="<?php echo htmlspecialchars($callbackUrl); ?>">
<input type="text" name="username" placeholder="用户名" required>
<input type="password" name="password" placeholder="密码" required>
<button type="submit">登录</button>
</form>
</div>
</body>
</html>
<?php
exit;
}
private function jsonResponse($status, $message, $data = null) {
http_response_code($status);
header('Content-Type: application/json');
echo json_encode([
'status' => $status,
'message' => $message,
'data' => $data
]);
exit;
}
}
// 路由处理
$action = $_GET['action'] ?? 'login';
$sso = new SSOServer();
switch ($action) {
case 'login':
$sso->handleLogin();
break;
case 'authenticate':
$sso->handleLoginSubmit();
break;
case 'validate':
$sso->validateToken();
break;
case 'logout':
$sso->handleLogout();
break;
default:
$sso->jsonResponse(400, 'Invalid action');
}
客户端应用集成
SSO客户端库
<?php
// common/SSOClient.php
class SSOClient {
private $ssoServerUrl;
private $appKey;
private $appSecret;
private $sessionPrefix;
public function __construct($config) {
$this->ssoServerUrl = $config['sso_server_url'];
$this->appKey = $config['app_key'];
$this->appSecret = $config['app_secret'];
$this->sessionPrefix = $config['session_prefix'] ?? 'sso_';
}
// 检查用户是否已登录
public function isLoggedIn() {
return isset($_SESSION[$this->sessionPrefix . 'user']);
}
// 获取当前用户
public function getUser() {
return $_SESSION[$this->sessionPrefix . 'user'] ?? null;
}
// 登录方法
public function login() {
// 检查是否有SSO回调
if (isset($_GET['sso_token'])) {
return $this->handleSSOCallback($_GET['sso_token']);
}
if (isset($_GET['sso_data'])) {
return $this->handleSSODataCallback($_GET['sso_data']);
}
// 跳转到SSO服务器
$callbackUrl = $this->getCurrentUrl();
$loginUrl = $this->ssoServerUrl . '/index.php?action=login&app_key=' .
$this->appKey . '&callback_url=' . urlencode($callbackUrl);
header('Location: ' . $loginUrl);
exit;
}
// 处理SSO回调
private function handleSSOCallback($token) {
// 调用SSO服务器验证令牌
$result = $this->validateTokenWithServer($token);
if ($result['status'] == 200) {
// 创建本地会话
$this->createLocalSession($result['data']['user']);
return true;
}
return false;
}
// 处理临时数据回调
private function handleSSODataCallback($encodedData) {
$data = json_decode(base64_decode($encodedData), true);
if (isset($data['token']) && $data['token']) {
return $this->handleSSOCallback($data['token']);
}
return false;
}
// 从SSO服务器验证令牌
private function validateTokenWithServer($token) {
$url = $this->ssoServerUrl . '/index.php?action=validate';
$data = [
'token' => $token,
'app_key' => $this->appKey,
'app_secret' => $this->appSecret
];
return $this->makeRequest($url, $data);
}
// 创建本地会话
private function createLocalSession($user) {
$_SESSION[$this->sessionPrefix . 'user'] = [
'id' => $user['id'],
'username' => $user['username'],
'email' => $user['email'],
'login_time' => time()
];
}
// 登出方法
public function logout() {
// 清理本地会话
unset($_SESSION[$this->sessionPrefix . 'user']);
// 跳转到SSO服务器登出
$logoutUrl = $this->ssoServerUrl . '/index.php?action=logout';
header('Location: ' . $logoutUrl);
exit;
}
// 获取当前URL
private function getCurrentUrl() {
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'];
$uri = $_SERVER['REQUEST_URI'];
return $protocol . '://' . $host . $uri;
}
// 发送HTTP请求
private function makeRequest($url, $data) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode == 200) {
return json_decode($response, true);
}
return ['status' => $httpCode, 'message' => 'Request failed'];
}
}
客户端应用示例
<?php
// client-app1/index.php
session_start();
require_once 'config.php';
require_once 'SSOClient.php';
$ssoConfig = [
'sso_server_url' => 'http://sso.example.com',
'app_key' => 'app1_key_123',
'app_secret' => 'app1_secret_456',
'session_prefix' => 'app1_'
];
$ssoClient = new SSOClient($ssoConfig);
// 检查是否已登录
if (!$ssoClient->isLoggedIn()) {
// 尝试自动登录
if (isset($_GET['sso_token']) || isset($_GET['sso_data'])) {
$loginResult = $ssoClient->login();
if (!$loginResult) {
// 登录失败,重定向到登录页面
header('Location: ' . $ssoConfig['sso_server_url'] . '/index.php?action=login&app_key=' . $ssoConfig['app_key'] . '&callback_url=' . urlencode($ssoConfig['sso_server_url'] . '/client-app1/index.php'));
exit;
}
} else {
// 跳转到SSO登录
$ssoClient->login();
}
}
// 获取当前用户
$user = $ssoClient->getUser();
// 处理登出
if (isset($_GET['logout'])) {
$ssoClient->logout();
}
?>
<!DOCTYPE html>
<html>
<head>应用1 - 用户首页</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; }
.header { display: flex; justify-content: space-between; align-items: center; }
.user-info { background: #f5f5f5; padding: 20px; border-radius: 5px; }
</style>
</head>
<body>
<div class="header">
<h2>应用1 - 用户首页</h2>
<div>
<span>当前用户: <?php echo htmlspecialchars($user['username'] ?? '未登录'); ?></span>
<a href="?logout=1">退出登录</a>
</div>
</div>
<div class="user-info">
<h3>用户信息</h3>
<p>用户名: <?php echo htmlspecialchars($user['username'] ?? ''); ?></p>
<p>邮箱: <?php echo htmlspecialchars($user['email'] ?? ''); ?></p>
<p>登录时间: <?php echo date('Y-m-d H:i:s', $user['login_time'] ?? time()); ?></p>
</div>
<div>
<a href="http://localhost:8081/app2/" target="_blank">访问应用2</a>
</div>
</body>
</html>
第二个客户端应用
<?php
// client-app2/index.php
session_start();
require_once 'config.php';
require_once 'SSOClient.php';
$ssoConfig = [
'sso_server_url' => 'http://sso.example.com',
'app_key' => 'app2_key_789',
'app_secret' => 'app2_secret_012',
'session_prefix' => 'app2_'
];
$ssoClient = new SSOClient($ssoConfig);
if (!$ssoClient->isLoggedIn()) {
if (isset($_GET['sso_token']) || isset($_GET['sso_data'])) {
$loginResult = $ssoClient->login();
if (!$loginResult) {
header('Location: ' . $ssoConfig['sso_server_url'] . '/index.php?action=login&app_key=' . $ssoConfig['app_key'] . '&callback_url=' . urlencode($ssoConfig['sso_server_url'] . '/client-app2/index.php'));
exit;
}
} else {
$ssoClient->login();
}
}
$user = $ssoClient->getUser();
?>
<!DOCTYPE html>
<html>
<head>应用2 - 用户首页</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; }
.header { display: flex; justify-content: space-between; align-items: center; }
.user-info { background: #f5f5f5; padding: 20px; border-radius: 5px; }
</style>
</head>
<body>
<div class="header">
<h2>应用2 - 用户首页</h2>
<div>
<span>当前用户: <?php echo htmlspecialchars($user['username'] ?? '未登录'); ?></span>
<a href="?logout=1">退出登录</a>
</div>
</div>
<div class="user-info">
<h3>用户信息</h3>
<p>用户名: <?php echo htmlspecialchars($user['username'] ?? ''); ?></p>
<p>邮箱: <?php echo htmlspecialchars($user['email'] ?? ''); ?></p>
</div>
<div>
<a href="http://localhost:8081/app1/" target="_blank">返回应用1</a>
</div>
</body>
</html>
安全考虑
令牌安全
<?php
// common/TokenManager.php
class TokenManager {
private $db;
private $secretKey;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
$this->secretKey = 'your-secret-key-for-token-encryption';
}
// 生成令牌
public function generateToken($userId) {
// 生成随机令牌
$token = bin2hex(random_bytes(32));
// 设置过期时间(默认2小时)
$expiresAt = date('Y-m-d H:i:s', time() + 7200);
// 保存到数据库
$sql = "INSERT INTO tokens (token, user_id, expires_at) VALUES (?, ?, ?)";
$stmt = $this->db->prepare($sql);
$stmt->execute([$token, $userId, $expiresAt]);
return $token;
}
// 验证令牌
public function validateToken($token) {
$sql = "SELECT t.*, u.username, u.email
FROM tokens t
JOIN users u ON t.user_id = u.id
WHERE t.token = ? AND t.expires_at > NOW()";
$stmt = $this->db->prepare($sql);
$stmt->execute([$token]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result) {
// 删除已使用的令牌(一次性使用)
$deleteSql = "DELETE FROM tokens WHERE token = ?";
$deleteStmt = $this->db->prepare($deleteSql);
$deleteStmt->execute([$token]);
return $result;
}
return false;
}
// 销毁令牌
public function destroyToken($token) {
$sql = "DELETE FROM tokens WHERE token = ?";
$stmt = $this->db->prepare($sql);
return $stmt->execute([$token]);
}
}
配置示例
<?php
// config.php
return [
'database' => [
'host' => 'localhost',
'dbname' => 'sso_system',
'user' => 'root',
'password' => 'your_password'
],
'sso_server' => [
'url' => 'http://sso.example.com',
'encryption_key' => 'your-256-bit-encryption-key'
],
'applications' => [
'app1' => [
'key' => 'app1_key_123',
'secret' => 'app1_secret_456',
'name' => '应用1',
'callback_url' => 'http://localhost:8081/app1/index.php'
],
'app2' => [
'key' => 'app2_key_789',
'secret' => 'app2_secret_012',
'name' => '应用2',
'callback_url' => 'http://localhost:8081/app2/index.php'
]
],
'session' => [
'name' => 'SSOSESSION',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]
];
部署建议
HTTPS强制
- 所有SSO通信必须通过HTTPS
- 配置HSTS头
- 使用安全的Cookie设置
会话管理
- 设置合理的会话过期时间
- 实现记住我功能
- 支持会话失效
日志监控
- 记录登录/登出日志
- 监控异常活动
- 设置访问次数限制
扩展功能
- 支持第三方登录(OAuth)
- 支持手机验证码
- 支持单点登出(SSO Logout)
这个SSO集成方案提供了一个完整的基础框架,您可以根据具体需求进行扩展和优化,记得在实际部署时添加适当的错误处理、日志记录和安全防护措施。