performer / php-saml

OneLogin PHP SAML 工具包

2.7.0-p2 2017-03-21 10:48 UTC

This package is auto-updated.

Last update: 2024-09-19 23:35:32 UTC


README

Build Status Coverage Status License

使用此库将 SAML 支持添加到您的 PHP 软件。忘记那些复杂的库,使用 OneLogin Inc. 提供和支持的开源库。

为什么要在我的软件中添加 SAML 支持?

SAML 是一种基于 XML 的网络浏览器单点登录标准,由 OASIS 安全服务技术委员会定义。该标准自 2002 年以来一直存在,但近年来由于其优势而越来越受欢迎。

  • 可用性 - 从门户或内网一键访问、深度链接、消除密码和自动续期会话使用户的生活更加便捷。
  • 安全性 - 基于强大的数字签名进行身份验证和完整性,SAML 是一个安全的单点登录协议,世界上最大和最注重安全的公司都依赖它。
  • 速度 - SAML 很快。只需一次浏览器重定向即可安全地将用户登录到应用程序。
  • 钓鱼防护 - 如果您没有应用程序的密码,您就不会被欺骗在假登录页面上输入它。
  • 对 IT 友好 - SAML 简化了 IT 的生活,因为它集中身份验证、提供更大的可见性并简化目录集成。
  • 机会 - B2B 云供应商应支持 SAML 以促进其产品的集成。

一般描述

OneLogin 的 SAML PHP 工具包允许您在 PHP 应用程序上构建 SP(服务提供商)并将其连接到任何 IdP(身份提供者)。

支持

  • SSO 和 SLO(SP 初始化和 IdP 初始化)。
  • 断言和 nameId 加密。
  • 断言签名。
  • 消息签名:AuthNRequest、LogoutRequest、LogoutResponses。
  • 启用断言消费者服务端点。
  • 启用单点登出服务端点。
  • 发布 SP 元数据(可以签名)。

主要功能

  • saml2int - 实现 SAML 2.0 Web 浏览器 SSO 配置文件。
  • 无会话 - 忘记那些 SP 和最终应用程序之间的常见冲突,此工具包将会话委托给最终应用程序。
  • 易于使用 - 开发人员将被允许编写高级和低级编程,提供 2 个易于使用的 API。
  • 经过测试 - 经彻底测试。
  • 受欢迎 - OneLogin 的客户使用它。许多 PHP SAML 插件使用它。

使用此指南在 OneLogin 中集成您的 PHP 工具包:https://developers.onelogin.com/page/saml-toolkit-for-php

安装

依赖关系

  • php >= 5.3.3 和一些核心扩展,如 php-xmlphp-datephp-zlib
  • openssl。安装 openssl 库。它处理 x509 证书。
  • mcrypt。如果您将要处理加密数据(如 nameIDassertions),请安装该库及其 php 驱动程序。
  • gettext。安装该库及其 php 驱动程序。它处理翻译。

由于 PHP 5.3 已正式不再支持,我们建议您使用较新的 PHP 版本。

代码

选项 1. 从 github 下载

此工具包托管在 github 上。您可以从中下载

将库的核心复制到PHP应用程序中。(每个应用程序都有自己的结构,所以请花时间找到最佳的PHP SAML工具包位置)。请参阅“添加SAML支持到我的应用的指南”以了解如何操作。

选项2. Composer

工具包支持composer。您可以在https://packagist.org.cn/packages/onelogin/php-saml找到onelogin/php-saml包。

要将SAML工具包导入到您当前的PHP项目中,请执行

composer require onelogin/php-saml

安装完成后,您将在vendor/文件夹中找到一个名为onelogin的新文件夹,并在其中包含php-saml。请确保您包括由composer提供的自动加载器。它可以在vendor/autoload.php中找到。

重要 在此选项中,x509证书必须存储在vendor/onelogin/php-saml/certs中,设置文件存储在vendor/onelogin/php-saml中。

当使用composer update或类似命令更新包时,您的设置可能会被删除。因此,强烈建议您不要使用设置文件,而是直接将设置作为数组传递给构造函数(本文件稍后解释)。如果您不采用此方法,则在更新包时,您的设置可能会被删除。

兼容性

此2.0版本有一个新的库。工具包仍然兼容。

您用于添加SAML支持的旧代码将继续工作,只需加载lib/Saml文件夹中的文件即可。(注意,compatibility.php文件会这样做)。

旧-demo文件夹包含使用工具包旧版本(v.1)的旧应用程序的代码。请查看。

有时旧代码中类的名称可能略有不同,如果是这种情况,您必须将它们更改为OneLogin_Saml_SettingsOneLogin_Saml_ResponseOneLogin_Saml_AuthRequestOneLogin_Saml_Metadata

我们建议您将旧代码迁移到新版本,以便能够使用新库Saml2的新功能。

命名空间

如果您正在使用像Symfony2这样的包含命名空间的框架,请记住,对类的调用必须通过在开头添加反斜杠(\)来执行,例如,要使用静态方法getSelfURLNoQuery,请使用

\OneLogin_Saml2_Utils::getSelfURLNoQuery()

安全警告

在生产环境中,strict参数必须设置为"true"。否则,您的环境将不安全,并可能遭受攻击。

入门

了解工具包

新的OneLogin SAML工具包包含不同的文件夹(certsendpointsextliblibdemo等)和一些文件。

让我们先描述一下文件夹

certs/

SAML需要x.509证书来签名和加密元素,如NameIDMessageAssertionMetadata

如果我们的环境需要签名或加密支持,此文件夹可能包含SP将使用的x509证书和私钥

  • sp.crt - SP的公共证书
  • sp.key - SP的私钥

或者我们也可以在设置文件的$settings['sp']['x509cert']$settings['sp']['privateKey']中提供这些数据。

有时我们可能需要在SP发布的元数据上添加签名,在这种情况下,我们可以使用前面提到的x.509证书或使用新的x.509证书:metadata.crtmetadata.key

extlib/

此文件夹包含工具包使用的第三方库。目前仅使用xmlseclibs(作者Robert Richards,BSD许可证),该库处理xml元素的签名和加密。

lib/

此文件夹包含工具包的核心,即库

  • Saml 文件夹包含工具包v.1的修改版本,允许旧代码继续工作。(此库提供以保持向后兼容性)。
  • Saml2 文件夹包含后面章节中描述的新版本类和方法。

doc/

此文件夹包含工具包的API文档。

endpoints/

工具包有三个端点

  • metadata.php - SP元数据发布的地方。
  • acs.php - 断言消费者服务。处理SAML响应。
  • sls.php - 单一注销服务。处理注销请求和注销响应。

您可以使用工具包提供的文件,或在添加SAML支持到您的应用程序时创建自己的端点文件。请注意,这些端点文件使用工具包基本文件夹的设置文件。

locale/

Locale 文件夹包含一些翻译:作为概念验证的 en_USes_ES。目前没有翻译,但最终我们将本地化消息并支持多种语言。

其他重要文件

  • settings_example_example.php - 创建包含工具包基本配置信息的settings.php文件时要使用的模板。
  • advanced_settings_example.php - 创建包含与安全性、联系人以及与SP关联的组织的额外配置信息的advanced_settings.php文件时要使用的模板。
  • _toolkit_loader.php - 此文件加载工具包库(SAML2库)。
  • compatibility - 导入该文件以使旧代码与新工具包兼容(加载SAML库)。

杂项

  • tests/ - 包含工具包的单元测试。
  • demo1/ - 包含具有SAML支持的简单PHP应用程序示例。阅读其中的 Readme.txt 获取更多信息。
  • demo2/ - 包含另一个示例。
  • demo-old/ - 包含使用工具包旧版本代码的示例,以演示向后兼容性。

工作原理

设置

首先,我们需要配置工具包。SP的信息、IdP的信息,以及在某些情况下,配置高级安全问题,如签名和加密。

有两种方式提供设置信息

  • 使用位于工具包基本文件夹中的 settings.php 文件。
  • 使用设置数据的数组,并将其直接提供给类的构造函数。

有一个模板文件,settings_example.php,因此您可以复制此文件,重命名并编辑它。

<?php

$settings = array (
    // If 'strict' is True, then the PHP Toolkit will reject unsigned 
    // or unencrypted messages if it expects them to be signed or encrypted.
    // Also it will reject the messages if the SAML standard is not strictly
    // followed: Destination, NameId, Conditions ... are validated too.
    'strict' => false,

    // Enable debug mode (to print errors).
    'debug' => false,

    // Service Provider Data that we are deploying.
    'sp' => array (
        // Identifier of the SP entity  (must be a URI)
        'entityId' => '',
        // Specifies info about where and how the <AuthnResponse> message MUST be
        // returned to the requester, in this case our SP.
        'assertionConsumerService' => array (
            // URL Location where the <Response> from the IdP will be returned
            'url' => '',
            // SAML protocol binding to be used when returning the <Response>
            // message. OneLogin Toolkit supports this endpoint for the 
            // HTTP-POST binding only.
            'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
        ),
        // Specifies info about where and how the <Logout Response> message MUST be
        // returned to the requester, in this case our SP.        
        'singleLogoutService' => array (
            // URL Location where the <Response> from the IdP will be returned
            'url' => '',
            // SAML protocol binding to be used when returning the <Response>
            // message. OneLogin Toolkit supports the HTTP-Redirect binding
            // only for this endpoint.
            'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
        ),
        // Specifies the constraints on the name identifier to be used to
        // represent the requested subject.
        // Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported.
        'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
        // Usually x509cert and privateKey of the SP are provided by files placed at
        // the certs folder. But we can also provide them with the following parameters
        'x509cert' => '',
        'privateKey' => '',

    ),

    // Identity Provider Data that we want connected with our SP.
    'idp' => array (
        // Identifier of the IdP entity  (must be a URI)
        'entityId' => '',
        // SSO endpoint info of the IdP. (Authentication Request protocol)
        'singleSignOnService' => array (
            // URL Target of the IdP where the Authentication Request Message 
            // will be sent.
            'url' => '',
            // SAML protocol binding to be used when returning the <Response>
            // message. OneLogin Toolkit supports the HTTP-Redirect binding
            // only for this endpoint.
            'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
        ),
        // SLO endpoint info of the IdP.
        'singleLogoutService' => array (
            // URL Location of the IdP where SLO Request will be sent.
            'url' => '',
            // SAML protocol binding to be used when returning the <Response>
            // message. OneLogin Toolkit supports the HTTP-Redirect binding
            // only for this endpoint.
            'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
        ),
        // Public x509 certificate of the IdP
        'x509cert' => '',
        /*
         *  Instead of use the whole x509cert you can use a fingerprint in order to 
         *  validate a SAMLResponse.
         *  (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,
         *   or add for example the -sha256 , -sha384 or -sha512 parameter)
         *
         *  If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to
         *  let the toolkit know which algorithm was used. Possible values: sha1, sha256, sha384 or sha512
         *  'sha1' is the default value.
         * 
         *  Notice that if you want to validate any SAML Message sent by the HTTP-Redirect binding, you
         *  will need to provide the whole x509cert.
         */
        // 'certFingerprint' => '',
        // 'certFingerprintAlgorithm' => 'sha1',
    ),
);

除了所需的设置数据(IdP、SP)之外,还可以定义其他信息。与存在用于基本信息的模板一样,工具包基本文件夹中还有一个名为 advanced_settings_example.php 的模板,您可以复制并重命名为 advanced_settings.php

<?php

$advancedSettings = array (

    // Security settings
    'security' => array (

        /** signatures and encryptions offered */

        // Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
        // will be encrypted.
        'nameIdEncrypted' => false,

        // Indicates whether the <samlp:AuthnRequest> messages sent by this SP 
        // will be signed.  [Metadata of the SP will offer this info]
        'authnRequestsSigned' => false,

        // Indicates whether the <samlp:logoutRequest> messages sent by this SP 
        // will be signed.
        'logoutRequestSigned' => false,

        // Indicates whether the <samlp:logoutResponse> messages sent by this SP 
        // will be signed.
        'logoutResponseSigned' => false,

        /* Sign the Metadata
         False || True (use sp certs) || array (
                                                    keyFileName => 'metadata.key',
                                                    certFileName => 'metadata.crt'
                                                )
        */
        'signMetadata' => false,


        /** signatures and encryptions required **/

        // Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest>
        // and <samlp:LogoutResponse> elements received by this SP to be signed.
        'wantMessagesSigned' => false,

        // Indicates a requirement for the <saml:Assertion> elements received by
        // this SP to be signed. [Metadata of the SP will offer this info]
        'wantAssertionsSigned' => false,

        // Indicates a requirement for the NameID received by
        // this SP to be encrypted.
        'wantNameIdEncrypted' => false,


        // Authentication context.
        // Set to false and no AuthContext will be sent in the AuthNRequest,
        // Set true or don't present thi parameter and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
        // Set an array with the possible auth context values: array ('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'),
        'requestedAuthnContext' => true,

        // Indicates if the SP will validate all received xmls.
        // (In order to validate the xml, 'strict' and 'wantXMLValidation' must be true).
        'wantXMLValidation' => true,

        // Algorithm that the toolkit will use on signing process. Options:
        //    'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
        //    'http://www.w3.org/2000/09/xmldsig#dsa-sha1'
        //    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
        //    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
        //    'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'
        'signatureAlgorithm' => 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
    ),

    // Contact information template, it is recommended to suply a
    // technical and support contacts.
    'contactPerson' => array (
        'technical' => array (
            'givenName' => '',
            'emailAddress' => ''
        ),
        'support' => array (
            'givenName' => '',
            'emailAddress' => ''
        ),
    ),

    // Organization information template, the info in en_US lang is
    // recomended, add more if required.
    'organization' => array (
        'en-US' => array(
            'name' => '',
            'displayname' => '',
            'url' => ''
        ),
    ),
);

在安全部分,您可以设置SP将如何处理消息和断言。联系IdP的管理员,并询问IdP期望什么,并决定SP将处理哪些验证以及SP将有什么要求,并将这些要求传达给IdP的管理员。

一旦我们知道可以配置哪些类型的数据,让我们谈谈工具包内部如何处理设置。

描述的设置文件(settings.phpadvanced_settings.php)如果构造函数中没有提供包含设置信息的其他数组,则由工具包加载。让我们看一些示例。

// Initializes toolkit with settings.php & advanced_settings files.
$auth = new OneLogin_Saml2_Auth();
//or
$settings = new OneLogin_Saml2_Settings();

// Initializes toolkit with the array provided.
$auth = new OneLogin_Saml2_Auth($settingsInfo);
//or
$settings = new OneLogin_Saml2_Settings($settingsInfo);

您可以在包含构造函数执行的文件中声明 $settingsInfo,或者将它们放在任何文件中,然后加载该文件以获取我们将在以下示例中看到的可用数组。

<?php

require_once 'custom_settings.php';  // The custom_settings.php contains a
                                     // $settingsInfo array.

$auth = new OneLogin_Saml2_Auth($settingsInfo);

如何加载库

为了使用工具库,您需要导入位于工具库基本目录中的 _toolkit_loader.php 文件。您可以按以下方式加载此文件

<?php

define("TOOLKIT_PATH", '/var/www/php-saml/');
require_once(TOOLKIT_PATH . '_toolkit_loader.php');

在此行之后,我们将能够使用工具库中的类(及其方法)(因为已加载了外部和 Saml2 库文件)。

如果您为 PHP-SAML 工具库的版本 1 编写了 SAML 应用程序的代码,则需要加载 compatibility.php 文件,该文件加载 SAML 库文件,以及 _toolkit_loader.php

该 SAML 库使用最新版本工具库的新类和方法,但保留旧类、方法和旧流程,以完成相同的功能。

我们强烈建议迁移旧代码并使用新工具库的新 API,因为有很多新功能您无法用旧代码处理。

启动 SSO

为了向 IdP 发送 AuthNRequest

<?php

define("TOOLKIT_PATH", '/var/www/php-saml/');
require_once(TOOLKIT_PATH . '_toolkit_loader.php');   // We load the SAML2 lib

$auth = new OneLogin_Saml2_Auth(); // Constructor of the SP, loads settings.php
                                   // and advanced_settings.php
$auth->login();   // Method that sent the AuthNRequest

根据 advanced_settings.php'authnRequestsSigned')中的安全信息,AuthNRequest 将以签名或未签名的方式发送。

然后,IdP 将返回 SAML 响应给用户的客户端。然后,客户端会带着这些信息被转发到 SP 的属性消费者服务。如果我们没有在登录方法中设置 'url' 参数,并且我们使用工具库提供的默认 ACS(endpoints/acs.php),则 ACS 端点将重定向用户到启动 SSO 请求的文件。

我们可以设置一个 'returnTo' URL 以更改工作流程并重定向用户到另一个 PHP 文件。

$newTargetUrl = 'http://example.com/consume2.php';
$auth = new OneLogin_Saml2_Auth();
$auth->login($newTargetUrl);

登录方法可以接收其他四个可选参数

  • $parameters - 一个参数数组,将在 HTTP-Redirect 中的 GET 中添加。
  • $forceAuthn - 当为 true 时,AuthNRequest 将设置 ForceAuthn='true'
  • $isPassive - 当为 true 时,AuthNRequest 将设置 Ispassive='true'
  • $strict - 如果我们想保留(返回 URL 字符串),则为 true;如果我们要重定向,则为 false

如果需要匹配未来的 SAMLResponse ID 和要发送的 AuthNRequest ID,则必须提取并保存该 AuthNRequest ID。

$ssoBuiltUrl = $auth->login(null, array(), false, false, true);
$_SESSION['AuthNRequestID'] = $auth->getLastRequestID();
header('Pragma: no-cache');
header('Cache-Control: no-cache, must-revalidate');
header('Location: ' . $ssoBuiltUrl);
exit();

SP 端点

与 SP 相关有三个重要视图:元数据视图、ACS 视图和 SLS 视图。工具库在 endpoints 目录中提供了这些视图的示例。

SP 元数据 endpoints/metadata.php

此代码将根据我们在设置文件中提供的信息提供我们的 SP 的 XML 元数据文件。

<?php

define("TOOLKIT_PATH", '/var/www/php-saml/');
require_once dirname(TOOLKIT_PATH.'/_toolkit_loader.php';

try {
    $auth = new OneLogin_Saml2_Auth();
    $settings = $auth->getSettings();
    $metadata = $settings->getSPMetadata();
    $errors = $settings->validateMetadata($metadata);
    if (empty($errors)) {
        header('Content-Type: text/xml');
        echo $metadata;
    } else {
        throw new OneLogin_Saml2_Error(
            'Invalid SP metadata: '.implode(', ', $errors),
            OneLogin_Saml2_Error::METADATA_SP_INVALID
        );
    }
} catch (Exception $e) {
    echo $e->getMessage();
}

getSPMetadata 将返回基于 advanced_settings.php'signMetadata')中的安全信息已签名或未签名的元数据。

在暴露 XML 元数据之前,将进行一次检查以确保要提供的信息有效。

除了使用 Auth 对象外,您还可以直接使用

$settings = new OneLogin_Saml2_Settings($settingsInfo, true);

来获取设置对象,并通过使用 true 参数来避免 IdP 设置验证。

属性消费者服务(ACS)endpoints/acs.php

此代码处理 IdP 通过用户的客户端转发给 SP 的 SAML 响应。

<?php

session_start();  // IMPORTANT: This is required in order to be able
                  // to store the user data in the session.

define("TOOLKIT_PATH", '/var/www/php-saml/');
require_once dirname(TOOLKIT_PATH.'/_toolkit_loader.php';

$auth = new OneLogin_Saml2_Auth();

if (isset($_SESSION) && isset($_SESSION['AuthNRequestID'])) {
    $requestID = $_SESSION['AuthNRequestID'];
} else {
    $requestID = null;
}

$auth->processResponse($requestID);

$errors = $auth->getErrors();

if (!empty($errors)) {
    print_r('<p>'.implode(', ', $errors).'</p>');
    exit();
}

if (!$auth->isAuthenticated()) {
    echo "<p>Not authenticated</p>";
    exit();
}

$_SESSION['samlUserdata'] = $auth->getAttributes();
$_SESSION['samlNameId'] = $auth->getNameId();
if (isset($_POST['RelayState']) && OneLogin_Saml2_Utils::getSelfURL() != $_POST['RelayState']) {
    $auth->redirectTo($_POST['RelayState']);
}

$attributes = $_SESSION['samlUserdata'];
$nameId = $_SESSION['samlNameId'];

echo '<h1>Identified user: '. htmlentities($nameId) .'</h1>';

if (!empty($attributes)) {
    echo '<h2>'._('User attributes:').'</h2>';
    echo '<table><thead><th>'._('Name').'</th><th>'._('Values').'</th></thead><tbody>';
    foreach ($attributes as $attributeName => $attributeValues) {
        echo '<tr><td>' . htmlentities($attributeName) . '</td><td><ul>';
        foreach ($attributeValues as $attributeValue) {
            echo '<li>' . htmlentities($attributeValue) . '</li>';
        }
        echo '</ul></td></tr>';
    }
    echo '</tbody></table>';
} else {
    echo _('No attributes found.');
}

SAML 响应被处理并检查是否存在错误。它还验证用户是否已认证,并将用户数据存储在会话中。

在此点有两个可能的方案

  1. 如果没有提供 RelayState,我们可以在该视图中显示用户数据或按需显示。

  2. 如果提供了 RelayState,则进行重定向。

注意,我们在重定向之前将用户数据保存在会话中,以便在 RelayState 视图中可用用户数据。

getAttributes 方法

为了检索属性,我们可以使用

$attributes = $auth->getAttributes();

使用此方法,我们可以获取SAML响应断言中IdP提供的所有用户数据。

如果我们执行 print_r($attributes),我们可能会得到

Array
(
    [cn] => Array
        (
            [0] => John
        )
    [sn] => Array
        (
            [0] => Doe
        )
    [mail] => Array
        (
            [0] => john.doe@example.com
        )
    [groups] => Array
        (
            [0] => users
            [1] => members
        )
)

每个属性名称都可以用作 $attributes 的索引来获取值。每个属性值都是一个数组 - 单值属性是一个只有一个元素的数组。

以下代码是等效的

$attributes = $auth->getAttributes();
print_r($attributes['cn']);
print_r($auth->getAttribute('cn'));

在尝试获取属性之前,请检查用户是否已认证。如果用户未认证或SAML断言中没有属性,则将返回空数组。例如,如果我们调用 getAttributes$auth->processResponse 之前,则 getAttributes() 将返回空数组。

单点登出服务(SLS)endpoints/sls.php

此代码处理登出请求和登出响应。

<?php

session_start();  // IMPORTANT: This is required in order to be able
                  // to close the user session.

define("TOOLKIT_PATH", '/var/www/php-saml/');
require_once dirname(TOOLKIT_PATH.'/_toolkit_loader.php';

$auth = new OneLogin_Saml2_Auth();

if (isset($_SESSION) && isset($_SESSION['LogoutRequestID'])) {
    $requestID = $_SESSION['LogoutRequestID'];
} else {
    $requestID = null;
}

$auth->processSLO(false, $requestID);

$errors = $auth->getErrors();

if (empty($errors)) {
    print_r('Sucessfully logged out');
} else {
    print_r(implode(', ', $errors));
}

如果SLS端点收到登出响应,将验证响应并关闭会话

// part of the processSLO method

$logoutResponse = new OneLogin_Saml2_LogoutResponse($this->_settings, $_GET['SAMLResponse']);
if (!$logoutResponse->isValid($requestId)) {
    $this->_errors[] = 'invalid_logout_response';
} else if ($logoutResponse->getStatus() !== OneLogin_Saml2_Constants::STATUS_SUCCESS) {
    $this->_errors[] = 'logout_not_success';
} else {
    if (!$keepLocalSession) {
        OneLogin_Saml2_Utils::deleteLocalSession();
    }
}

如果SLS端点收到登出请求,则验证请求,关闭会话并向IdP的SLS端点发送登出响应。

// part of the processSLO method

$decoded = base64_decode($_GET['SAMLRequest']);
$request = gzinflate($decoded);
if (!OneLogin_Saml2_LogoutRequest::isValid($this->_settings, $request)) {
    $this->_errors[] = 'invalid_logout_request';
} else {
    if (!$keepLocalSession) {
        OneLogin_Saml2_Utils::deleteLocalSession();
    }

    $inResponseTo = $request->id;
    $responseBuilder = new OneLogin_Saml2_LogoutResponse($this->_settings);
    $responseBuilder->build($inResponseTo);
    $logoutResponse = $responseBuilder->getResponse();

    $parameters = array('SAMLResponse' => $logoutResponse);
    if (isset($_GET['RelayState'])) {
        $parameters['RelayState'] = $_GET['RelayState'];
    }

    $security = $this->_settings->getSecurityData();
    if (isset($security['logoutResponseSigned']) && $security['logoutResponseSigned']) {
        $signature = $this->buildResponseSignature($logoutResponse, $parameters['RelayState'], $security['signatureAlgorithm']);
        $parameters['SigAlg'] = $security['signatureAlgorithm'];
        $parameters['Signature'] = $signature;
    }

    $this->redirectTo($this->getSLOurl(), $parameters);
}

如果您不是使用默认PHP会话,或者需要手动销毁会话,可以将回调方法传递给 processSLO 方法作为第四个参数

$keepLocalSession = False;
$callback = function () {
    // Destroy user session
};

$auth->processSLO($keepLocalSession, null, false, $callback);

如果我们不想让 processSLO 销毁会话,请将一个true参数传递给 processSLO 方法

$keepLocalSession = True;
$auth->processSLO($keepLocalSession);

启动SLO

为了向IdP发送登出请求

<?php

define("TOOLKIT_PATH", '/var/www/php-saml/');
require_once(TOOLKIT_PATH . '_toolkit_loader.php');

$auth = new OneLogin_Saml2_Auth();

$auth->logout();   // Method that sent the Logout Request.

此外,还可以设置三个可选参数

  • $name_id - 将用于构建登出请求。如果未设置 name_id 参数,并且auth对象处理了带有 NameId 的SAML响应,则将使用此 NameId
  • $session_index - 识别用户会话的SessionIndex。
  • $strict - 如果我们想要停留(返回URL字符串),则为False以重定向。

根据 advanced_settings.php'logoutRequestSigned')中的安全信息,登出请求将签名或未签名地发送。

IdP将通过用户的客户端返回登出响应到SP的单点登出服务。如果我们没有在登出方法中设置 'url' 参数并使用工具箱提供的默认SLS(endpoints/sls.php),则SLS端点将重定向用户到启动SLO请求的文件。

我们可以设置一个 'returnTo' URL来更改工作流程并将用户重定向到其他PHP文件。

$newTargetUrl = 'http://example.com/loggedOut.php';
$auth = new OneLogin_Saml2_Auth();
$auth->logout($newTargetUrl);

如果需要将未来的登出响应ID与要发送的登出请求ID进行匹配,则必须提取并存储该登出请求ID。

$sloBuiltUrl = $auth->logout(null, $paramters, $nameId, $sessionIndex, true);
$_SESSION['LogoutRequestID'] = $auth->getLastRequestID();
header('Pragma: no-cache');
header('Cache-Control: no-cache, must-revalidate');
header('Location: ' . $sloBuiltUrl);
exit();

初始化SSO请求和处理的视图示例(是acs目标)

我们可以编写一个独特的文件来启动SSO过程,处理响应,获取属性,启动SLO并处理登出响应。

注意:请参阅包含该用例的 demo1 文件夹;在稍后的部分中,我们将更详细地解释demo1用例。

<?php

session_start();    // Initialize the session, we do that because
                    // Note that processResponse and processSLO
                    // methods could manipulate/close that session

require_once dirname(dirname(__FILE__)).'/_toolkit_loader.php'; // Load Saml2 and
                                                                // external libs
require_once 'settings.php';    // Load the setting info as an Array

$auth = new OneLogin_Saml2_Auth($settingsInfo);  // Initialize the SP SAML instance

if (isset($_GET['sso'])) {    // SSO action.  Will send an AuthNRequest to the IdP
    $auth->login();
} else if (isset($_GET['sso2'])) {              // Another SSO action 
    $returnTo = $spBaseUrl.'/demo1/attrs.php';  // but set a custom RelayState URL
    $auth->login($returnTo);
} else if (isset($_GET['slo'])) {  // SLO action. Will sent a Logout Request to IdP
    $auth->logout();
} else if (isset($_GET['acs'])) {  // Assertion Consumer Service
    $auth->processResponse();      // Process the Response of the IdP, get the
                                   // attributes and put then at 
                                   // $_SESSION['samlUserdata']

    $errors = $auth->getErrors();  // This method receives an array with the errors
                                   // that could took place during the process 

    if (!empty($errors)) {
        print_r('<p>'.implode(', ', $errors).'</p>');
    }
                                          // This check if the response was 
    if (!$auth->isAuthenticated()) {      // sucessfully validated and the user
        echo "<p>Not authenticated</p>";  // data retrieved or not 
        exit();
    }

    $_SESSION['samlUserdata'] = $auth->getAttributes(); // Retrieves user data
    if (isset($_POST['RelayState']) && OneLogin_Saml2_Utils::getSelfURL() != $_POST['RelayState']) {
        $auth->redirectTo($_POST['RelayState']);  // Redirect if there is a 
    }                                             // relayState set
} else if (isset($_GET['sls'])) {   // Single Logout Service
    $auth->processSLO();            // Process the Logout Request & Logout Response
    $errors = $auth->getErrors(); // Retrieves possible validation errors
    if (empty($errors)) {
        print_r('<p>Sucessfully logged out</p>');
    } else {
        print_r('<p>'.implode(', ', $errors).'</p>');
    }
}

if (isset($_SESSION['samlUserdata'])) {   // If there is user data we print it.
    if (!empty($_SESSION['samlUserdata'])) {
        $attributes = $_SESSION['samlUserdata'];
        echo 'You have the following attributes:<br>';
        echo '<table><thead><th>Name</th><th>Values</th></thead><tbody>';
        foreach ($attributes as $attributeName => $attributeValues) {
            echo '<tr><td>' . htmlentities($attributeName) . '</td><td><ul>';
            foreach ($attributeValues as $attributeValue) {
                echo '<li>' . htmlentities($attributeValue) . '</li>';
            }
            echo '</ul></td></tr>';
        }
        echo '</tbody></table>';
    } else {                             // If there is not user data, we notify
        echo "<p>You don't have any attribute</p>";
    }

    echo '<p><a href="?slo" >Logout</a></p>'; // Print some links with possible 
} else {                                      // actions
    echo '<p><a href="?sso" >Login</a></p>';
    echo '<p><a href="?sso2" >Login and access to attrs.php page</a></p>';
}

主要类和方法

以下是可以调用的主要类和方法。

旧Saml库

让我们开始描述SAML库的类和方法,这是一个旧v.1工具包的演变,它被提供以保持向后兼容性。大多数它们使用新SAML2库的类和方法。

OneLogin_Saml_AuthRequest - AuthRequest.php

具有受保护的属性 $auth,一个 OneLogin_Saml2_Auth 对象。

  • OneLogin_Saml_AuthRequest - 构建 OneLogin_Saml2_Auth,初始化SP SAML实例。
  • getRedirectUrl($returnTo) - 获取包含压缩AuthRequest消息的SSO URL。
OneLogin_Saml_Response - Response.php
  • OneLogin_Saml_Response - 构造函数用于处理SAML响应,内部初始化SP SAML实例和OneLogin_Saml2_Response
  • get_saml_attributes - 获取包含登录用户数据的数组。
OneLogin_Saml_Settings - Settings.php

一个简单的类,用于构建工具包v1.0中使用的设置对象。

OneLogin_Saml_Metadata - Metadata.php
  • OneLogin_Saml_Metadata - 构造函数根据SP的设置构建元数据XML信息。
  • getXml - 包含SP元数据信息的XML。
OneLogin_Saml_XmlSec - XmlSec.php

一个辅助类,包含验证SAML响应的方法:validateNumAssertionsvalidateTimestampsisValid(使用前两种方法并验证SAML响应的签名)。

Saml2库

现在我们来描述SAML2库中的类和方法。

OneLogin_Saml2_Auth - Auth.php

OneLogin PHP工具包的主要类

  • OneLogin_Saml2_Auth - 初始化SP SAML实例
  • login - 启动SSO过程。
  • logout - 启动SLO过程。
  • processResponse - 处理由IdP发送的SAML响应。
  • processSLO - 处理由IdP发送的SAML注销响应/注销请求。
  • redirectTo - 将用户重定向到通过参数传递的URL或我们定义的SSO请求中的URL。
  • isAuthenticated - 检查用户是否已认证。
  • getAttributes - 返回SAML属性集。
  • getAttribute - 返回请求的SAML属性
  • getNameId - 返回nameID
  • getSessionIndex - 从AuthnStatement获取SessionIndex。
  • getErrors - 返回是否有错误
  • getSSOurl - 获取SSO URL。
  • getSLOurl - 获取SLO URL。
  • getLastRequestID - 最后生成的SAML消息的ID。
  • buildRequestSignature - 生成SAML请求的签名。
  • buildResponseSignature - 生成SAML响应的签名。
  • getSettings - 返回设置信息
  • setStrict - 设置严格模式启用/禁用
OneLogin_Saml2_AuthnRequest - AuthnRequest.php

SAML 2认证请求类

  • OneLogin_Saml2_AuthnRequest - 构造AuthnRequest对象。
  • getRequest - 返回压缩、base64编码、未签名的AuthnRequest
  • getId - 返回AuthNRequest ID。
OneLogin_Saml2_Response - Response.php

SAML 2认证响应类

  • OneLogin_Saml2_Response - 构造SAML响应对象。
  • isValid - 使用证书确定SAML响应是否有效。
  • checkStatus - 检查状态是否成功。
  • getAudiences - 获取受众。
  • getIssuers - 获取发行者(来自响应和断言)
  • getNameIdData - 获取由IdP提供的NameID数据。
  • getNameId - 获取由IdP提供的NameID。
  • getSessionNotOnOrAfter - 从AuthnStatement获取SessionNotOnOrAfter。
  • getSessionIndex - 从AuthnStatement获取SessionIndex。
  • getAttributes - 从AttributeStatement元素获取属性。
  • validateNumAssertions - 验证文档只包含单个断言(加密或未加密)。
  • validateTimestamps - 验证文档根据条件元素仍然有效。
  • getError - 执行验证过程后,如果失败,此方法返回原因
OneLogin_Saml2_LogoutRequest - LogoutRequest.php

SAML 2注销请求类

  • OneLogin_Saml2_LogoutRequest - 构造注销请求对象。
  • getRequest - 返回压缩、base64编码、未签名的注销请求
  • getID - 返回登出请求的ID。(如果您有该对象,您可以通过id属性访问它)
  • getNameIdData - 获取登出请求的NameID数据。
  • getNameId - 获取登出请求的NameID。
  • getIssuer - 获取登出请求的发行者。
  • getSessionIndexes - 从登出请求中获取SessionIndexes。
  • isValid - 检查接收到的登出请求是否有效。
  • getError - 执行验证过程后,如果失败,此方法返回原因
OneLogin_Saml2_LogoutResponse - LogoutResponse.php

SAML 2登出响应类

  • OneLogin_Saml2_LogoutResponse - 构造一个登出响应对象(从设置中初始化参数,如果提供则加载登出响应)
  • getIssuer - 获取登出响应的发行者。
  • getStatus - 获取登出响应的状态。
  • isValid - 判断SAML登出响应是否有效
  • build - 生成一个登出响应对象。
  • getResponse - 返回一个登出响应对象。
  • getError - 执行验证过程后,如果失败,此方法返回原因
OneLogin_Saml2_Settings - Settings.php

OneLogin PHP工具包的配置

  • OneLogin_Saml2_Settings - 初始化设置:设置不同文件夹的路径,并从设置文件或提供的数组/对象中加载设置信息
  • checkSettings - 检查设置信息。
  • getBasePath - 返回基本路径。
  • getCertPath - 返回证书路径。
  • getLibPath - 返回库路径。
  • getExtLibPath - 返回外部库路径。
  • getSchemasPath - 返回模式路径。
  • checkSPCerts - 检查SP的x509证书是否存在且有效。
  • getSPkey - 返回SP的x509私钥。
  • getSPcert - 返回SP的x509公钥。
  • getIdPData - 获取IdP数据。
  • getSPData 获取SP数据。
  • getSecurityData - 获取安全数据。
  • getContacts - 获取联系数据。
  • getOrganization - 获取组织数据。
  • getSPMetadata - 获取SP元数据。XML表示形式。
  • validateMetadata - 验证XML SP元数据。
  • formatIdPCert - 格式化IdP证书。
  • formatSPCert - 格式化SP证书。
  • formatSPKey - 格式化SP私钥。
  • getErrors - 返回一个包含错误信息的数组,当设置正常时数组为空。
  • getLastErrorReason * 返回最后错误的理由
  • setStrict - 启用或禁用严格模式。
  • isStrict - 返回是否启用了'严格'模式。
  • isDebugActive - 返回是否启用了调试。
OneLogin_Saml2_Metadata - Metadata.php

一个包含与SP元数据相关功能的类

  • builder - 根据设置生成SP的元数据。
  • signmetadata - 使用提供的密钥/证书签名元数据
  • addX509KeyDescriptors - 将x509描述符(签名/加密)添加到元数据中
OneLogin_Saml2_Utils - Utils.php

一个包含多个方法的辅助类

  • validateXML - 此函数尝试根据指定的模式验证XML字符串。
  • formatCert - 返回一个x509证书(如果需要则添加头部和尾部)。
  • formatPrivateKey - 返回一个RSA私钥(如果需要则添加头部和尾部)。
  • redirect - 执行到提供的url的重定向(或返回目标url)。
  • isHTTPS - 检查是否为https或http。
  • getSelfHost - 返回当前主机。
  • getSelfURLhost - 返回协议 + 当前主机 + 端口(如果与常见端口不同)。
  • getSelfURLNoQuery - 返回当前主机 + 当前视图的URL。
  • getSelfURL - 返回当前主机 + 当前视图 + 查询的 URL。
  • generateUniqueID - 生成一个唯一的字符串(例如,用作断言的 ID)。
  • parseTime2SAML - 将 UNIX 时间戳转换为 SAML2 时间戳,格式为 yyyy-mm-ddThh:mm:ss(.s+)?Z
  • parseSAML2Time - 将格式为 yyyy-mm-ddThh:mm:ss(.s+)?Z 的 SAML2 时间戳转换为 UNIX 时间戳。忽略亚秒部分。
  • parseDuration - 解释相对于给定时间戳的 ISO8601 持续时间值。
  • getExpireTime - 比较两个日期并返回最早的一个。
  • query - 从 DOMDocument 中提取节点。
  • isSessionStarted - 检查会话是否已启动。
  • deleteLocalSession - 删除本地会话。
  • calculateX509Fingerprint - 计算一个 x509cert 的指纹。
  • formatFingerPrint - 格式化一个指纹。
  • generateNameId - 生成一个 nameID
  • getStatus - 从响应中获取状态。
  • decryptElement - 解密一个加密的元素。
  • castKey - 将 XMLSecurityKey 转换为正确的算法。
  • addSign - 向元素(消息或断言)添加签名密钥和发送者证书。
  • validateSign - 验证签名(消息或断言)。

有关更多信息,请查看源代码;每个方法都有文档,并提供了关于做什么以及如何使用它的详细信息。请确保还检查文档文件夹,其中提供了有关 SAML 和 SAML2 的类和方法的 HTML 文档。

工具包中包含的演示

该工具包包括三个演示应用程序,以教授如何使用该工具包,请查看它。

在测试之前,演示需要配置得很好的 SP 和 IdP。

演示1

SP 设置

Onelogin 的 PHP Toolkit 允许您以两种方式提供设置信息

  • 使用位于工具包基本文件夹中的 settings.php 文件。
  • 使用设置数据数组。

在这个演示中,我们使用名为 $settingsInfo 的设置数组以第二种方式提供数据。此数组使用包含的 settings_example.php 作为模板来创建 settings.php 设置并将其存储在 demo1/ 文件夹中。配置 SP 部分,然后检查 IdP 的元数据并完成 IdP 信息。

如果您检查 index.php 文件的代码,您将看到 settings.php 文件被忽略,并使用位于工具包基本文件夹中的 _toolkit_loader.php 加载库。

注意,在这个演示中,可以定义在工具包基本文件夹中的 setting.php 文件被忽略,并且使用位于工具包基本文件夹中的 _toolkit_loader.php 加载库。

IdP 设置

一旦 SP 配置完成,SP 的元数据就会发布在 metadata.php 文件中。根据这些信息配置 IdP。

工作原理

  1. 首次访问 index.php 视图时,您可以选择登录并返回到同一视图或登录并重定向到 attrs.php 视图。

  2. 当您点击

    第一个链接中的 2.1,我们访问 (index.php?sso),向 IdP 发送一个 AuthNRequest,在 IdP 进行身份验证,然后通过用户的客户端将响应发送到 SP,特别是断言消费者服务视图:index.php?acs。注意设置了 RelayState 参数,指向启动过程的 URL,即 index.php 视图。

    第二个链接中的 2.2 我们访问 (attrs.php) 与 2.1 中描述的过程相同,区别在于 RelayState 设置为 attrs.php

  3. 在 ACS (index.php?acs) 处处理 SAML 响应,如果响应无效,则在此处停止处理并显示消息。否则,我们将重定向到 RelayState 视图。a) index.php 或 b) attrs.php

  4. 我们在应用程序中登录,并显示了用户属性。此时,我们可以测试单次注销功能。

  5. 可以通过两种方式测试单次注销功能。

    5.1 由SP发起的SLO。在SP上单击“注销”链接,之后向IdP发送注销请求,关闭IdP上的会话,并通过客户端向SP发送注销响应(发送到单次注销服务端点)。SP的SLS端点(index.php?sls)处理注销响应,如果有效,关闭本地应用程序的用户会话。请注意,SLO工作流程始于SP并结束于SP。

    5.2 由IdP发起的SLO。在这种情况下,操作发生在IdP端,注销过程在idP处启动,向SP发送注销请求(SLS端点,index.php?sls)。SP的SLS端点处理注销请求,如果有效,关闭本地应用程序中用户会话,并向IdP发送注销响应(发送到IdP的SLS端点)。IdP接收注销响应,处理它并关闭IdP的会话。请注意,SLO工作流程始于IdP并结束于IdP。

请注意,所有SAML请求和响应都在一个唯一的文件中处理,即index.php文件,以及如何使用GET参数来了解必须执行的操作。

Demo2

SP 设置

Onelogin 的 PHP Toolkit 允许您以两种方式提供设置信息

  • 使用位于工具包基本文件夹中的 settings.php 文件。
  • 使用设置数据数组。

第一种是demo2应用程序的情况。应在工具包的基本文件夹中定义setting.php文件和setting_extended.php文件。查看setting_example.phpadvanced_settings_example.php,了解如何构建它们。

在这种情况下,作为属性消费服务和单次注销服务,我们将使用位于端点文件夹中的文件(acs.phpsls.php)。

IdP 设置

一旦配置了SP,SP的元数据将发布在metadata.php文件中。基于这些信息,配置IdP。

工作原理

在demo1中,我们看到了所有SAML请求和响应都由一个唯一的文件处理,即index.php文件。此demo1使用高级编程。

在demo2中,我们有几个视图:index.phpsso.phpslo.phpconsume.phpmetadata.php。正如我们所说的,我们将使用工具包中定义的端点(端点文件夹中的acs.phpsls.php)。此demo2使用低级编程。

请注意,SSO操作可以在index.phpsso.php中启动。

发生的SAML工作流程与demo1中定义的工作流程类似,只是改变了目标。

  1. 第一次访问index.phpsso.php时,会自动向IdP发送AuthNRequest(因为发送了原始url作为RelayState)。我们在IdP处进行身份验证,然后向SP发送Response,到ACS端点,在这种情况下是端点文件夹中的acs.php

  2. 在ACS中处理SAML响应,如果响应无效,则在此停止处理并显示消息。否则,我们将重定向到RelayState视图(sso.phpindex.php)。sso.php检测用户是否已登录,并将其重定向到index.php,因此我们将最终停留在index.php

  3. 我们已经登录到应用程序,并显示了用户属性(如果有的话)。此时,我们可以测试单次注销功能。

  4. 可以通过两种方式测试单次注销功能。

    4.1 由服务提供者(SP)发起的SLO。在SP上点击“登出”链接后,我们将重定向到 slo.php 视图,并在那里向身份提供者(IdP)发送登出请求。在IdP上关闭会话,并向SP发送登出响应(发送到单点登出服务端点)。在这种情况下,SP的SLS端点处理登出响应,如果有效,则关闭本地应用的会话。请注意,SLO工作流始于SP并结束于SP。

    5.2 由身份提供者(IdP)发起的SLO。在这种情况下,操作发生在IdP端,登出过程在IdP端启动,向SP发送登出请求(端点文件夹中的SLS端点 sls.php)。SP的SLS端点处理登出请求,如果有效,则关闭本地应用的会话,并向IdP发送登出响应(发送到IdP的SLS端点)。IdP收到登出响应,处理它并关闭IdP的会话。请注意,SLO工作流始于IdP并结束于IdP。

旧版演示

SP 设置

本演示使用工具包的版本1的旧样式。必须向 AuthRequest 的构造函数提供一个 OneLogin_Saml_Settings 类的实例。

在demo-old文件夹中,您将找到一个 example_settings.php 文件,可以用作您自己的 settings.php 文件的模板。

在该模板中,SAML设置分为两部分,应用特定的(const_assertion_consumer_service_urlconst_issuerconst_name_identifier_format)和用户/账户特定的(idp_sso_target_urlx509certificate)。您需要在此处添加自己的代码来识别用户或用户来源(例如,通过subdomainip_address等)。

IdP 设置

配置SP后,SP的元数据将在 metadata.php 文件中发布。之后,根据该信息配置IdP。

工作原理

metadata.php 视图中发布了SP的元数据。

index.php 文件在需要由应用程序发起SAML对话时充当发起者。这被称为服务提供者发起的SAML。服务提供者创建SAML身份验证请求并将其发送到身份提供者(IdP)。

consume.php 是ACS端点。接收SAML断言。在响应验证后,将通过 getNameId()getAttributes() 使用户数据和nameID可用。

由于php工具包的版本1不支持SLO,因此在本demo-old中不展示如何处理SLO。