本文目录导读:

在PHP项目中实现SAML(安全断言标记语言)认证,通常有两种主流方式:使用现成的库(如onelogin/php-saml)或使用轻量级库(如lightSAML),以下是完整的实现指南,以最流行的onelogin/php-saml为例。
核心概念
SAML工作流程中涉及三个角色:
- SP(服务提供者):你的PHP应用
- IdP(身份提供者):如Azure AD、Okta、Keycloak等
- 用户(浏览器):最终使用者
使用 onelogin/php-saml 实现
安装依赖
composer require onelogin/php-saml
项目结构建议
your-app/
├── saml/
│ ├── settings.php # SAML配置
│ ├── acs.php # Assertion Consumer Service(断言消费者服务)
│ ├── login.php # SP发起登录
│ ├── logout.php # 单点登出
│ └── metadata.php # 生成SP元数据
├── index.php # 受保护页面示例
└── vendor/
核心配置 (saml/settings.php)
<?php
// 定义常量
define('SAML_PATH', __DIR__);
define('SP_BASE_URL', 'https://your-app.com/saml');
// SP(服务提供者)配置
$sp_settings = [
'entityId' => SP_BASE_URL . '/metadata.php',
'assertionConsumerService' => [
'url' => SP_BASE_URL . '/acs.php',
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
],
'singleLogoutService' => [
'url' => SP_BASE_URL . '/logout.php',
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
],
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
'x509cert' => '', // SP的证书(如果需要对SAML响应签名)
'privateKey' => '', // SP的私钥
];
// IdP(身份提供者)配置 - 从IdP获取
$idp_settings = [
'entityId' => 'https://your-idp.com/auth/realms/master', // IdP的实体ID
'singleSignOnService' => [
'url' => 'https://your-idp.com/auth/realms/master/protocol/saml',
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
],
'singleLogoutService' => [
'url' => 'https://your-idp.com/auth/realms/master/protocol/saml/logout',
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
],
'x509cert' => file_get_contents(SAML_PATH . '/certs/idp.crt'), // IdP的公钥证书
];
// 合并配置
$settings = new \OneLogin\Saml2\Settings([
'sp' => $sp_settings,
'idp' => $idp_settings,
'security' => [
'authnRequestsSigned' => true, // 对SAML请求签名
'wantAssertionsSigned' => true, // 要求断言被签名
'wantNameIdEncrypted' => false, // 是否加密NameID
'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256',
],
'strict' => true, // 生产环境建议true
'debug' => true, // 调试用,生产关闭
]);
return $settings;
SP发起登录 (saml/login.php)
<?php
session_start();
require_once 'vendor/autoload.php';
$settings = require 'saml/settings.php';
$auth = new \OneLogin\Saml2\Auth($settings);
// 可选:保存当前URL,登录后重定向
$_SESSION['return_to'] = $_GET['return_to'] ?? '/index.php';
// 检查是否有登录失败标记
if (isset($_GET['error'])) {
echo "认证失败: " . htmlspecialchars($_GET['error_msg']);
exit;
}
// 重定向到IdP的SSO URL
$auth->login(SP_BASE_URL . '/acs.php'); // 参数:重定向目标URL
处理SAML响应 (saml/acs.php)
<?php
session_start();
require_once 'vendor/autoload.php';
$settings = require 'saml/settings.php';
$auth = new \OneLogin\Saml2\Auth($settings);
// 处理SAML响应
$auth->processResponse();
// 检查错误
$errors = $auth->getErrors();
if (!empty($errors)) {
echo "SAML响应验证失败: " . implode(', ', $errors);
exit;
}
// 获取用户信息
$attributes = $auth->getAttributes();
$nameId = $auth->getNameId();
$sessionIndex = $auth->getSessionIndex();
// 在PHP Session中保存用户信息
$_SESSION['auth'] = true;
$_SESSION['user'] = [
'name_id' => $nameId,
'email' => $attributes['email'][0] ?? null,
'name' => $attributes['firstName'][0] . ' ' . $attributes['lastName'][0],
'groups' => $attributes['groups'] ?? [],
];
// 重定向到原来请求的页面
$redirect = $_SESSION['return_to'] ?? '/index.php';
unset($_SESSION['return_to']);
header('Location: ' . $redirect);
exit;
单点登出 (saml/logout.php)
<?php
session_start();
require_once 'vendor/autoload.php';
$settings = require 'saml/settings.php';
$auth = new \OneLogin\Saml2\Auth($settings);
// 检查是否有IdP发起的登出请求
if (isset($_GET['SAMLRequest'])) {
// IdP发起的登出
$auth->processSLO();
session_destroy();
header('Location: /index.php');
exit;
}
// SP发起的登出
$nameId = $_SESSION['user']['name_id'] ?? null;
$sessionIndex = $_SESSION['user']['session_index'] ?? null;
// 清除本地Session
session_destroy();
// 重定向到IdP的登出URL
$auth->logout('/index.php', $nameId, $sessionIndex);
exit;
生成SP元数据 (saml/metadata.php)
<?php
require_once 'vendor/autoload.php';
$settings = require 'saml/settings.php';
$auth = new \OneLogin\Saml2\Auth($settings);
$metadata = $auth->getSettings()->getSPMetadata();
// 验证元数据
$errors = $auth->getSettings()->validateMetadata($metadata);
if (empty($errors)) {
header('Content-Type: text/xml');
echo $metadata;
} else {
echo "元数据验证错误: " . implode(', ', $errors);
}
受保护页面示例 (index.php)
<?php
session_start();
if (!isset($_SESSION['auth']) || !$_SESSION['auth']) {
// 未登录,重定向到登录
header('Location: /saml/login.php?return_to=' . urlencode($_SERVER['REQUEST_URI']));
exit;
}
// 用户已登录
$user = $_SESSION['user'];
?>
<!DOCTYPE html>
<html>
<head>受保护的页面</title>
</head>
<body>
<h1>欢迎, <?= htmlspecialchars($user['name']) ?></h1>
<p>姓名: <?= htmlspecialchars($user['name']) ?></p>
<p>邮箱: <?= htmlspecialchars($user['email']) ?></p>
<p>用户组: <?= implode(', ', $user['groups']) ?></p>
<p><a href="/saml/logout.php">登出</a></p>
</body>
</html>
与IdP集成步骤
- 获取SP元数据:访问
https://your-app.com/saml/metadata.php,将XML内容导入IdP。 - 配置IdP:在IdP侧(如Azure AD):
- 上传SP元数据或手动配置ACS URL、Audience URI
- 注册用户属性映射(如 email->email, firstName->given_name)
- 获取IdP配置:从IdP获取:
- IdP元数据URL或XML内容
- 实体ID(Issuer)
- SSO服务URL
- 签名证书(x509cert)
生产环境注意事项
- HTTPS强制使用:所有SAML端点和回调URL必须使用HTTPS。
- 保持时间同步:SAML使用时间戳,服务器时间偏差应<5分钟(使用NTP)。
- 严格模式:生产环境设置
strict => true。 - 防止CSRF:SAML Auth本身有RelayState机制防CSRF,但应用层仍需注意。
- Session管理:建议使用Redis等外部Session存储,便于跨实例共享。
- 错误处理:不要在生产环境显示详细错误信息。
常见问题调试
- 签名验证失败:检查证书格式(必须为PEM格式,以
-----BEGIN CERTIFICATE-----开头) - 时间偏差错误:运行
date命令检查服务器时间,配置NTP - 响应格式错误:在acs.php中添加
file_put_contents('/tmp/saml_response.xml', $_POST['SAMLResponse'])查看原始响应 - 属性映射问题:检查IdP是否发送了正确的属性,使用
$auth->getAttributes()打印
替代方案
- lightSAML:更轻量,依赖少,适合简单场景
composer require lightsaml/lightsaml
- SimpleSAMLphp:完整的SAML框架,功能强大但较重
- 自建实现:使用XML解析和OpenSSL函数手动处理(不推荐)
推荐使用onelogin/php-saml,因为它被广泛使用、维护良好、文档完善,且符合SAML 2.0标准。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播