nbgrp/onelogin-saml-bundle

OneLogin SAML Symfony Bundle

安装次数: 433,226

依赖项: 0

建议者: 0

安全性: 0

星标: 43

关注者: 5

分支: 15

公开问题: 13

类型:symfony-bundle


README

Latest Stable Version Latest Unstable Version Total Downloads License Gitter

PHP Version Require Codecov Audit

SymfonyInsight

概述

OneLogin SAML Symfony Bundle.

此包依赖于 Symfony 6 及更高版本。
对于旧版本的 Symfony,您可以使用 hslavich/oneloginsaml-bundle,该包以此包为基础。

兼容性

安装

composer require nbgrp/onelogin-saml-bundle

如果您使用 Symfony Flex,它将自动启用该包。否则,要启用该包,请在 config/bundles.php 中添加以下代码

return [
    // ...
    Nbgrp\OneloginSamlBundle\NbgrpOneloginSamlBundle::class => ['all' => true],
];

配置

要配置该包,您需要在 config/packages/nbgrp_onelogin_saml.yaml 中添加配置。您可以使用任何配置格式(yaml、xml 或 php),但为了方便,本文件中将使用 yaml。

有关 OneLogin PHP SAML 设置的更多信息,请参阅 https://github.com/onelogin/php-saml#settings

您可以在以下配置值中使用占位符 <request_scheme_and_host>,它将被从 Request 对象中替换为相应的值。

  • onelogin_settings.sp.entityId
  • onelogin_settings.sp.assertionConsumerService.url
  • onelogin_settings.sp.singleLogoutService.url
  • onelogin_settings.baseurl

如果您在负载均衡器或反向代理后面运行应用程序,请注意 可信代理设置

nbgrp_onelogin_saml:
    onelogin_settings:
        default:
            # Mandatory SAML settings
            idp:
                entityId: 'https://id.example.com/saml2/idp/metadata.php'
                singleSignOnService:
                    url: 'https://id.example.com/saml2/idp/SSOService.php'
                    binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
                singleLogoutService:
                    url: 'https://id.example.com/saml2/idp/SingleLogoutService.php'
                    binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
                x509cert: 'MIIC...'
            sp:
                entityId: 'https://myapp.com/saml/metadata'  #  Default: '<request_scheme_and_host>/saml/metadata'
                assertionConsumerService:
                    url: 'https://myapp.com/saml/acs'  #  Default: '<request_scheme_and_host>/saml/acs'
                    binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
                singleLogoutService:
                    url: 'https://myapp.com/saml/logout'  #  Default: '<request_scheme_and_host>/saml/logout'
                    binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
                privateKey: 'MIIE...'
            # Optional SAML settings
            baseurl: 'https://myapp.com/saml/'  #  Default: '<request_scheme_and_host>/saml/'
            strict: true
            debug: true
            security:
                nameIdEncrypted: false
                authnRequestsSigned: false
                logoutRequestSigned: false
                logoutResponseSigned: false
                signMetadata: false
                wantMessagesSigned: false
                wantAssertionsEncrypted: false
                wantAssertionsSigned: true
                wantNameId: false
                wantNameIdEncrypted: false
                requestedAuthnContext: true
                requestedAuthnContextComparison: 'exact'
                wantXMLValidation: false
                relaxDestinationValidation: false
                destinationStrictlyMatches: true
                allowRepeatAttributeName: false
                rejectUnsolicitedResponsesWithInResponseTo: false
                signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
                digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256'
                encryption_algorithm: 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'
                lowercaseUrlencoding: false
            contactPerson:
                technical:
                    givenName: 'Tech User'
                    emailAddress: 'techuser@example.com'
                support:
                    givenName: 'Support User'
                    emailAddress: 'supportuser@example.com'
                administrative:
                    givenName: 'Administrative User'
                    emailAddress: 'administrativeuser@example.com'
            organization:
                en-US:
                    name: 'Example'
                    displayname: 'Example'
                    url: 'http://example.com'
            compress:
                requests: false
                responses: false
        # Optional another one SAML settings (see Multiple IdP below)
        another:
            idp:
                # ...
            sp:
                # ...
            # ...
    # Optional parameters
    use_proxy_vars: true
    idp_parameter_name: 'custom-idp'
    entity_manager_name: 'custom-em'

对于 idpsp 部分,还有一些额外的参数。您可以从 OneLogin PHP SAML 文档中了解更多信息。

除了指定 IdP 和 SP 的 x509 证书和私钥之外,您还可以将它们存储在 OneLogin PHP SAML 的 certs 目录 中,或使用全局常量 ONELOGIN_CUSTOMPATH 来指定自定义目录(完整路径将为 ONELOGIN_CUSTOMPATH.'certs/')。

如果您不想设置某些联系人或组织信息,请不要添加这些参数,而是保留为空白。

config/packages/security.yaml 中配置用户提供者和防火墙

security:
    # ...

    providers:
        saml_provider:
            ##  Basic provider instantiates a user with identifier and default roles
            saml:
                user_class: 'App\Entity\User'
                default_roles: ['ROLE_USER']

    firewalls:
        main:
            pattern: ^/
            saml:
                ##  Match SAML attribute 'uid' with user identifier.
                ##  Otherwise, used \OneLogin\Saml2\Auth::getNameId() method by default.
                identifier_attribute: uid
                ##  Use the attribute's friendlyName instead of the name.
                use_attribute_friendly_name: true
                check_path: saml_acs
                login_path: saml_login
            logout:
                path: saml_logout

    access_control:
        - { path: ^/saml/(metadata|login|acs), roles: PUBLIC_ACCESS }
        - { path: ^/, roles: ROLE_USER }

编辑您的 config/routes.yaml

nbgrp_saml:
    resource: "@NbgrpOneloginSamlBundle/Resources/config/routes.php"

多个 IdP

您可以为多个 IdP 配置多个 OneLogin PHP SAML 设置。为此,您需要为每个 IdP 指定 SAML 设置(配置中具有 defaultanother 键的部分)并通过查询字符串参数 idp 或同名请求属性传递必要的 IdP 名称。您可以使用 idp_parameter_name 包参数使用其他名称。

要使用适当的 SAML 设置,所有对包路由的请求都必须包含正确的 IdP 参数。

如果请求没有包含 IdP 值的查询参数或属性,则将使用 onelogin_settings 部分中的第一个键作为默认 IdP。

使用反向代理

当您在反向代理后面使用您的应用程序并使用 X-Forwarded-* 头时,您需要设置参数 nbgrp_onelogin_saml.use_proxy_vars = true,以允许底层的 OneLogin 库正确确定请求协议、主机和端口。

可选功能

将 SAML 属性注入到 User 对象中

要能够将 SAML 属性注入到用户对象中,您必须实现 SamlUserInterface

<?php

namespace App\Entity;

use Nbgrp\OneloginSamlBundle\Security\User\SamlUserInterface;

class User implements SamlUserInterface
{
    private $username;
    private $email;

    // ...

    public function setSamlAttributes(array $attributes)
    {
        $this->email = $attributes['mail'][0];
    }
}

除了将 SAML 属性注入到用户之外,您还可以通过当前安全令牌(应为 Nbgrp\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken 的实例)的 getAttributes 方法获取它们。

与经典登录表单集成

您可以通过编辑您的 security.yaml 来将 SAML 验证与传统的登录表单集成。

security:
    enable_authenticator_manager: true

    providers:
        user_provider:
            # ...

    firewalls:
        main:
            saml:
                # ...

            ##  Traditional login form
            form_login:
                login_path: /login
                check_path: /login_check
                # ...

            logout:
                path: saml_logout

然后您可以在登录页面中添加指向路由 saml_login 的链接,以启动 SAML 登录。

<a href="{{ path('saml_login') }}">SAML Login</a>

如果您使用多个 IdP,您应通过 path 参数指定它。

<a href="{{ path('saml_login', { idp: 'another' }) }}">SAML Login</a>

即时用户配置

为了对用户进行配置,您必须使用会抛出 UserNotFoundException 的用户提供者(例如,在上面的示例中使用的 EntityUserProvider)。SamlUserProvider 不会抛出此异常,这将导致返回一个空的用户(如果您的用户类没有实现 Nbgrp\OneloginSamlBundle\Security\User\SamlUserInterface)。

当用户提供者找不到用户时,可以根据接收到的 SAML 属性创建新的用户配置。

通过编辑 services.yaml 创建用户工厂服务。

services:
    saml_user_factory:
        class: Nbgrp\OneloginSamlBundle\Security\User\SamlUserFactory
        arguments:
            ##  User class
            - App\Entity\User
            ##  Attribute mapping
            - password: 'notused'
              email: $mail
              name: $cn
              lastname: $sn
              roles: ['ROLE_USER']
              groups: $groups[]

将值以 '$' 开头的映射项引用到 SAML 属性值。
以 '[]' 结尾的值将作为数组呈现(即使它们最初是标量)。

然后,将创建的服务 ID 作为 user_factory 参数添加到您的防火墙设置中的 security.yaml

security:
    # ...

    providers:
        saml_provider:
            ##  Loads user from user repository
            entity:
                class: App\Entity\User
                property: username

    firewalls:
        main:
            provider: saml_provider
            saml:
                identifier_attribute: uid
                ##  User factory service
                user_factory: saml_user_factory
                # ...

此外,您还可以创建自己的 User Factory,该工厂实现 Nbgrp\OneloginSamlBundle\Security\User\SamlUserFactoryInterface

<?php

namespace App\Security;

use App\Entity\User;
use Nbgrp\OneloginSamlBundle\Security\User\SamlUserFactoryInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class UserFactory implements SamlUserFactoryInterface
{
    public function createUser(string $identifier, array $attributes): UserInterface
    {
        $user = new User();
        $user->setRoles(['ROLE_USER']);
        $user->setUsername($username);
        $user->setEmail($attributes['mail'][0]);
        // ...

        return $user;
    }
}

并将其添加到 services.yaml

services:
    saml_user_factory:
        class: App\Security\UserFactory

在创建时持久化用户和注入 SAML 属性

需要 Symfony EventDispatcher 组件和 Doctrine ORM。

如果您想在成功认证后持久化用户对象,您需要在 security.yaml 的防火墙设置中添加 persist_user

security:
    # ...

    firewalls:
        # ...

        main:
            saml:
                # ...
                persist_user: true

要使用非默认实体管理器,请在 nbgrp_onelogin_saml.entity_manager_name 包配置参数中指定其名称。

用户持久化是通过事件监听器 Nbgrp\OneloginSamlBundle\EventListener\User\UserCreatedListenerNbgrp\OneloginSamlBundle\EventListener\User\UserModifiedListener 实现的,您可以根据需要对其进行装饰以覆盖默认行为。

此外,您还可以为 Nbgrp\OneloginSamlBundle\Event\UserCreatedEventNbgrp\OneloginSamlBundle\Event\UserModifiedEvent 事件创建自己的监听器。

<?php

namespace App\Security;

use Nbgrp\OneloginSamlBundle\Event\UserCreatedEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener(event: UserCreatedEvent::class, dispatcher: 'security.event_dispatcher.main')]
class UserCreatedListener
{
    public function __invoke(UserCreatedEvent $event): void
    {
        // ...
    }
}

重要:您必须指定触发事件的防火墙对应的 dispatcher 选项(在上面的示例中为 main)。有关更多信息,请参阅 Security Events