korotovsky / sso-idp-bundle
Symfony2的单点登录套餐。身份提供者部分。
Requires
- php: >=5.5
- ext-openssl: *
- doctrine/orm: ~2.3
- korotovsky/sso-library: ~0.3.1
- symfony/console: ~3.1|~4.0
- symfony/form: ~3.1|~4.0
- symfony/framework-bundle: ~2.8|~3.0|~4.0
- symfony/security-bundle: ~3.1|~4.0
- symfony/templating: ~3.1|~4.0
- symfony/twig-bundle: ~3.1|~4.0
Requires (Dev)
- doctrine/doctrine-bundle: ~1.6
- doctrine/doctrine-fixtures-bundle: ~2.3
- phpunit/phpunit: ^5.7
- symfony/var-dumper: ^3.4|^4.1
README
免责声明
我绝非安全专家。我对它也不差,但我不能保证这个套餐的安全性。如果你想在生产中使用它,请自担风险。话虽如此,如果你想为使这个套餐更好/更安全做出贡献,你始终可以创建一个问题或发送一个拉取请求。
描述
此套餐提供了一种轻松集成网站单点登录的方法。它使用现有的(主要)防火墙进行实际认证,并将所有配置的单点登录路由重定向以通过一次性密码进行认证。
安装
安装过程分为10步
- 使用Composer下载SingleSignOnIdentityProviderBundle
- 启用套餐
- 创建服务提供者
- 配置SingleSignOnIdentityProviderBundle
- 启用验证OTP的路由
- 修改安全设置
- 添加/修改登录和注销成功处理器
- 创建OTP路由
- 添加重定向路径到登录表单
- 更新数据库模式
步骤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流程。
- sso_identity_provider.service_manager – 与SP交互的管理器。通过提供的SP标识符,返回一个
\Krtv\Bundle\SingleSignOnIdentityProviderBundle\Manager\ServiceProviderInterface
实例。 - sso_identity_provider.otp_manager – 与OTP令牌交互的管理器。验证、无效化和接收。
- sso_identity_provider.uri_signer – 用于签名URL的服务,如果您需要自行将用户重定向到/sso/login。
这就是身份提供者了。现在您可以继续配置 服务提供者部分