本文目录导读:

我将为您提供一个完整的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集成方案提供了一个完整的基础框架,您可以根据具体需求进行扩展和优化,记得在实际部署时添加适当的错误处理、日志记录和安全防护措施。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播