korotovsky/sso-idp-bundle

Symfony2的单点登录套餐。身份提供者部分。

0.3.4 2018-10-08 08:25 UTC

This package is not auto-updated.

Last update: 2024-09-23 11:06:31 UTC


README

Build Status Scrutinizer Code Quality Code Coverage SensioLabsInsight

免责声明

我绝非安全专家。我对它也不差,但我不能保证这个套餐的安全性。如果你想在生产中使用它,请自担风险。话虽如此,如果你想为使这个套餐更好/更安全做出贡献,你始终可以创建一个问题或发送一个拉取请求

描述

此套餐提供了一种轻松集成网站单点登录的方法。它使用现有的(主要)防火墙进行实际认证,并将所有配置的单点登录路由重定向以通过一次性密码进行认证。

安装

安装过程分为10步

  1. 使用Composer下载SingleSignOnIdentityProviderBundle
  2. 启用套餐
  3. 创建服务提供者
  4. 配置SingleSignOnIdentityProviderBundle
  5. 启用验证OTP的路由
  6. 修改安全设置
  7. 添加/修改登录和注销成功处理器
  8. 创建OTP路由
  9. 添加重定向路径到登录表单
  10. 更新数据库模式

步骤1:使用Composer下载SingleSignOnIdentityProviderBundle

告诉Composer需要此包

composer require korotovsky/sso-idp-bundle

Composer会将套餐安装到您的项目目录vendor/korotovsky

步骤2:启用套餐

<?php
// app/AppKernel.php

public function registerBundles()
{
    $bundles = [
        // ...
        new Krtv\Bundle\SingleSignOnIdentityProviderBundle\KrtvSingleSignOnIdentityProviderBundle(),
    ];
}
?>

步骤3:创建服务提供者

您必须为每个使用SSO SP套餐的应用程序创建一个ServiceProvider。

每个ServiceProvider都必须实现Krtv\Bundle\SingleSignOnIdentityProviderBundle\Manager\ServiceProviderInterface

<?php
// src/AcmeBundle/ServiceProviders/Consumer1.php

namespace AcmeBundle\ServiceProviders;

use Krtv\Bundle\SingleSignOnIdentityProviderBundle\Manager\ServiceProviderInterface;

/**
 * Consumer 1 service provider
 */
class Consumer1 implements ServiceProviderInterface
{
    /**
     * Get name of the service
     *
     * @return string
     */
    public function getName()
    {
        return 'consumer1';
    }

    /**
     * Get service provider index url
     *
     * @param  array  $parameters
     *
     * @return string
     */
    public function getServiceIndexUrl($parameters = [])
    {
        return 'http://consumer1.com/';
    }

    /**
     * Get service provider logout url
     *
     * @param  array  $parameters
     *
     * @return string
     */
    public function getServiceLogoutUrl($parameters = [])
    {
        return 'http://consumer1.com/logout';
    }
}
?>
<?php
// src/AcmeBundle/ServiceProviders/Consumer2.php

namespace AcmeBundle\ServiceProviders;

use Krtv\Bundle\SingleSignOnIdentityProviderBundle\Manager\ServiceProviderInterface;

/**
 * Consumer 2 service provider
 */
class Consumer2 implements ServiceProviderInterface
{
    /**
     * Get name of the service
     *
     * @return string
     */
    public function getName()
    {
        return 'consumer2';
    }

    /**
     * Get service provider index url
     *
     * @param  array  $parameters
     *
     * @return string
     */
    public function getServiceIndexUrl($parameters = [])
    {
        return 'http://consumer2.com/';
    }

    /**
     * Get service provider logout url
     *
     * @param  array  $parameters
     *
     * @return string
     */
    public function getServiceLogoutUrl($parameters = [])
    {
        return 'http://consumer2.com/logout';
    }
}
?>

并将它们定义为服务。

# app/config/services.yml
services:
    acme_bundle.sso.consumer1:
        class: AcmeBundle\ServiceProviders\Consumer1
        tags:
            - { name: sso.service_provider, service: consumer1 }

    acme_bundle.sso.consumer2:
        class: AcmeBundle\ServiceProviders\Consumer2
        tags:
            - { name: sso.service_provider, service: consumer2 }

步骤4:配置SingleSignOnIdentityProviderBundle

该套餐依赖于现有的防火墙来提供实际认证。为此,您必须配置单点登录登录路径位于该防火墙之后,并确保您需要认证才能访问该路由。

将以下设置添加到您的config.yml

# app/config/config.yml:
krtv_single_sign_on_identity_provider:
    host:             idp.example.com
    host_scheme:      http

    login_path:       /sso/login/
    logout_path:      /sso/logout

    services:
        - consumer1
        - consumer2

    otp_parameter:    _otp
    secret_parameter: secret

步骤5:启用验证OTP的路由

# app/config/routing.yml
sso:
    resource: .
    type:     sso

步骤6:修改安全设置

# app/config/security.yml
security:
    access_control:
        # We need to allow users to access the /sso/login route
        # without being logged in
        - { path: ^/sso/login, role: IS_AUTHENTICATED_ANONYMOUSLY }

步骤7:添加/修改登录和注销成功处理器

修改您的现有构造函数以包括以下服务

sso_identity_provider.otp_manager

在用作成功处理程序的方法中,在末尾附近调用该服务的clear()方法。

如果您没有登录或注销的成功处理程序,以下是一个示例实现

<?php
// src/AcmeBundle/Handler/LoginSuccessHandler.php

namespace AcmeBundle\Handler;

use Krtv\Bundle\SingleSignOnIdentityProviderBundle\Manager\ServiceManager;
use Krtv\Bundle\SingleSignOnIdentityProviderBundle\Manager\ServiceProviderInterface;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\UriSigner;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;

/**
 * Class LoginSuccessHandler.
 */
class LoginSuccessHandler implements AuthenticationSuccessHandlerInterface
{
    /**
     * @var ServiceManager
     */
    protected $serviceManager;

    /**
     * @var UriSigner
     */
    protected $uriSigner;

    /**
     * @var SessionInterface
     */
    protected $session;

    /**
     * @var Router
     */
    protected $router;

    /**
     * @param ServiceManager $serviceManager
     * @param UriSigner $uriSigner
     * @param SessionInterface $session
     * @param Router $router
     */
    public function __construct(
        ServiceManager $serviceManager,
        UriSigner $uriSigner,
        SessionInterface $session,
        Router $router
    ) {
        $this->serviceManager = $serviceManager;
        $this->uriSigner = $uriSigner;
        $this->session = $session;
        $this->router = $router;
    }

    /**
     * @param Request $request
     * @param TokenInterface $token
     *
     * @return RedirectResponse
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token)
    {
        $redirectUrl = $this->session->get('_security.main.target_path', '/');

        if ($request->query->has('_target_path')) {
            if ($this->uriSigner->check($request->query->get('_target_path'))) {
                $redirectUrl = $request->query->get('_target_path');
            }
        }

        if (strpos($redirectUrl, '/sso/login') === false) {
            $targetService = $this->serviceManager->getSessionService();

            if ($targetService != null) {
                $redirectUrl = $this->getSsoWrappedUrl($token, $targetService, $redirectUrl);
            } else {
                $redirectUrl = $this->router->generate('_passport_dashboard_index');
            }
        }

        $this->serviceManager->clear();

        if ($request->isXmlHttpRequest()) {
            return new JsonResponse([
                'status' => true,
                'location' => $redirectUrl,
            ]);
        }

        return new RedirectResponse($redirectUrl);
    }

    /**
     * @param TokenInterface $token
     * @param string $targetService
     * @param string $redirectUrl
     *
     * @return string
     */
    protected function getSsoWrappedUrl(TokenInterface $token, $targetService, $redirectUrl)
    {
        /** @var $serviceManager ServiceProviderInterface */
        $serviceManager = $this->serviceManager->getServiceManager($targetService);
        $owner = $token->getUser();

        $wrappedSsoUrl = $this->router->generate('sso_login_path', [
            '_target_path' => $serviceManager->getOTPValidationUrl([
                '_target_path' => $redirectUrl,
            ]),
            'service' => $targetService,
        ], Router::ABSOLUTE_URL);

        return $this->uriSigner->sign($wrappedSsoUrl);
    }
}
?>
<?php
// src/AcmeBundle/Handler/LogoutSuccessHandler.php

namespace AcmeBundle\Handler;

use Krtv\Bundle\SingleSignOnIdentityProviderBundle\Manager\ServiceManager;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\UriSigner;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;

/**
* Class LogoutSuccessHandler
*/
class LogoutSuccessHandler implements LogoutSuccessHandlerInterface
{
    /**
     * @var ServiceManager;
     */
    protected $serviceManager;

    /**
     * @var UriSigner
     */
    protected $uriSigner;

    /**
     * @var SessionInterface
     */
    protected $session;

    /**
     * @var Router
     */
    protected $router;

    /**
     * Constructor
     *
     * @param ServiceManager   $serviceManager
     * @param UriSigner        $uriSigner
     * @param SessionInterface $session
     * @param Router           $router
     */
    public function __construct(
        ServiceManager $serviceManager,
        UriSigner $uriSigner,
        SessionInterface $session,
        Router $router
    ) {
        $this->serviceManager = $serviceManager;
        $this->uriSigner = $uriSigner;
        $this->session = $session;
        $this->router = $router;
    }

    /**
     * Logout success handler
     *
     * @param  Request        $request
     *
     * @return RedirectResponse|JsonResponse
     */
    public function onLogoutSuccess(Request $request)
    {
        $redirectUrl = $this->session->get('_security.main.target_path', '/');

        if ($request->query->has('_target_path')) {
            if ($this->uriSigner->check($request->query->get('_target_path'))) {
                $redirectUrl = $request->query->get('_target_path');
            }
        }

        $this->serviceManager->clear();

        if ($request->isXmlHttpRequest()) {
            return new JsonResponse([
                'status' => true,
                'location' => $redirectUrl,
            ]);
        }

        return new RedirectResponse($redirectUrl);
    }
}
?>

将它们定义为服务

# app/config/services.yml
services:
    acme_bundle.security.login_success_handler:
        class: AcmeBundle\Handler\LoginSuccessHandler
        arguments:
            - "@sso_identity_provider.service_manager"
            - "@sso_identity_provider.uri_signer"
            - "@session"
            - "@router"

    acme_bundle.security.logout_success_handler:
        class: AcmeBundle\Handler\LogoutSuccessHandler
        arguments:
            - "@sso_identity_provider.service_manager"
            - "@sso_identity_provider.uri_signer"
            - "@session"
            - "@router"

然后最终,将服务设置为防火墙定义中的处理程序

# app/config/security.yml
security:
    firewall:
        main:
            # ...
            form_login:
                # ...
                success_handler: acme_bundle.security.login_success_handler

            logout:
                # ...
                success_handler: acme_bundle.security.logout_success_handler

步骤8:创建OTP路由

为了验证OTP并认证用户,您必须创建一个可以从中检索OTP详细信息并验证其有效性的路由。

路由路径并不重要,但请注意它。它将在SP套餐中使用。在我们的示例中,路由是/internal/v1/sso

<?php
// src/AcmeBundle/Controller/OtpController.php

namespace AcmeBundle\Controller;

use Krtv\SingleSignOn\Model\OneTimePassword;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

class OtpController extends Controller
{
    /**
     * Method used for retrieving of the OTP
     *
     * @Route("/internal/v1/sso", name="sso_otp")
     * @Method("GET")
     *
     * @param  Request $request
     *
     * @return JsonResponse
     */
    public function indexAction(Request $request)
    {
        /** @var \Krtv\SingleSignOn\Manager\OneTimePasswordManagerInterface */
        $otpManager = $this->get('sso_identity_provider.otp_manager');

        $pass = str_replace(' ', '+', $request->query->get('_otp'));

        /** @var \Krtv\SingleSignOn\Model\OneTimePasswordInterface */
        $otp = $otpManager->get($pass);

        if (!($otp instanceof OneTimePassword) || $otp->getUsed() === true) {
            throw new BadRequestHttpException('Invalid OTP password');
        }

        $response = [
            'data' => [
                'created_at' => $otp->getCreated()->format('r'),
                'hash' => $otp->getHash(),
                'password' => $otp->getPassword(),
                'is_used' => $otp->getUsed(),
            ],
        ];

        $otpManager->invalidate($otp);

        return new JsonResponse($response);
    }
}
?>

步骤9:添加重定向路径到登录表单

在您的登录表单中,添加一个具有名称_target_path和值{{ app.session.get('_security.main.target_path') }}的隐藏输入,如下所示

<input type="hidden" name="_target_path" value="{{ app.session.get('_security.main.target_path') }}" />

这将用于在登录后重定向用户到OTP验证路由。

步骤10:更新数据库模式

要能够存储OTP,您必须运行以下命令

php bin/console doctrine:schema:update --force

此套餐的公共API

此包将多个服务注册到服务容器中。这些服务将帮助您自定义应用程序中的SSO流程。

这就是身份提供者了。现在您可以继续配置 服务提供者部分