Расширение Symfony2 DefaultAuthenticationSuccessHandler

Я хочу изменить процесс аутентификации по умолчанию сразу после успеха проверки подлинности. Я сделал службу, которая называется после аутентификации и перед redirectм.

namespace Pkr\BlogUserBundle\Handler; use Doctrine\ORM\EntityManager; use Pkr\BlogUserBundle\Service\Encoder\WpTransitionalEncoder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Log\LoggerInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\Security\Http\Authentication\Response; class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface { protected $entityManager = null; protected $logger = null; protected $encoder = null; public function __construct(EntityManager $entityManager, LoggerInterface $logger, WpTransitionalEncoder $encoder) { $this->entityManager = $entityManager; $this->logger = $logger; $this->encoder = $encoder; } /** * This is called when an interactive authentication attempt succeeds. This * is called by authentication listeners inheriting from * AbstractAuthenticationListener. * * @param Request $request * @param TokenInterface $token * * @return Response never null */ public function onAuthenticationSuccess(Request $request, TokenInterface $token) { $user = $token->getUser(); $newPass = $request->get('_password'); $user->setUserPassword($this->encoder->encodePassword($newPass, null)); $this->entityManager->persist($user); $this->entityManager->flush(); //do redirect } } 

в services.yml

 services: pkr_blog_user.wp_transitional_encoder: class: "%pkr_blog_user.wp_transitional_encoder.class%" arguments: cost: "%pkr_blog_user.wp_transitional_encoder.cost%" logger: @logger pkr_blog_user.login_success_handler: class: Pkr\BlogUserBundle\Handler\AuthenticationSuccessHandler arguments: entity_manager: @doctrine.orm.entity_manager logger: @logger encoder: @pkr_blog_user.wp_transitional_encoder 

и в security.yml

 firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false secured_area: pattern: ^/ anonymous: ~ form_login: login_path: pkr_blog_admin_login check_path: pkr_blog_admin_login_check success_handler: pkr_blog_user.login_success_handler logout: path: pkr_blog_admin_logout target: / 

Я пытаюсь добиться того, чтобы немного изменить поведение по умолчанию, поэтому я думаю, почему бы не расширить DefaultAuthenticationSuccessHandler , добавить что-то в onSuccessHandler() и вызвать parent::onSucessHandler() . Я попытался, и проблема в том, что я не знаю, как добавить параметры безопасности (установленные в security.yml) в мой расширенный конструктор classов. DefaultAuthenticationSuccessHandler использует массив HttpUtils и $ options:

 /** * Constructor. * * @param HttpUtils $httpUtils * @param array $options Options for processing a successful authentication attempt. */ public function __construct(HttpUtils $httpUtils, array $options) { $this->httpUtils = $httpUtils; $this->options = array_merge(array( 'always_use_default_target_path' => false, 'default_target_path' => '/', 'login_path' => '/login', 'target_path_parameter' => '_target_path', 'use_referer' => false, ), $options); } 

Поэтому мой расширенный конструктор classов должен выглядеть так:

  // class extends DefaultAuthenticationSuccessHandler protected $entityManager = null; protected $logger = null; protected $encoder = null; public function __construct(HttpUtils $httpUtils, array $options, EntityManager $entityManager, LoggerInterface $logger, WpTransitionalEncoder $encoder) { $this->entityManager = $entityManager; $this->logger = $logger; $this->encoder = $encoder; } 

Довольно легко добавить службу HttpUtils в мои services.yml , но что с аргументом options?

 services: pkr_blog_user.wp_transitional_encoder: class: "%pkr_blog_user.wp_transitional_encoder.class%" arguments: cost: "%pkr_blog_user.wp_transitional_encoder.cost%" logger: @logger pkr_blog_user.login_success_handler: class: Pkr\BlogUserBundle\Handler\AuthenticationSuccessHandler arguments: httputils: @security.http_utils options: [] #WHAT TO ADD HERE ? entity_manager: @doctrine.orm.entity_manager logger: @logger encoder: @pkr_blog_user.wp_transitional_encoder 

    Если у вас есть только один обработчик успеха / отказа, определенный для вашего приложения, есть несколько более простой способ сделать это. Вместо того, чтобы определять новую службу для success_handler и failure_handler , вы можете переопределить security.authentication.success_handler и security.authentication.failure_handler .

    Пример:

    services.yml

     services: security.authentication.success_handler: class: StatSidekick\UserBundle\Handler\AuthenticationSuccessHandler arguments: ["@security.http_utils", {}] tags: - { name: 'monolog.logger', channel: 'security' } security.authentication.failure_handler: class: StatSidekick\UserBundle\Handler\AuthenticationFailureHandler arguments: ["@http_kernel", "@security.http_utils", {}, "@logger"] tags: - { name: 'monolog.logger', channel: 'security' } 

    AuthenticationSuccessHandler.php

     isXmlHttpRequest() ) { $response = new JsonResponse( array( 'success' => true, 'username' => $token->getUsername() ) ); } else { $response = parent::onAuthenticationSuccess( $request, $token ); } return $response; } } 

    AuthenticationFailureHandler.php

     isXmlHttpRequest() ) { $response = new JsonResponse( array( 'success' => false, 'message' => $exception->getMessage() ) ); } else { $response = parent::onAuthenticationFailure( $request, $exception ); } return $response; } } 

    В моем случае я просто пытался что-то настроить, чтобы получить ответ JSON, когда я пытаюсь аутентифицироваться с помощью AJAX, но принцип тот же.

    Преимущество такого подхода заключается в том, что без какой-либо дополнительной работы все параметры, которые обычно передаются в обработчики по умолчанию, должны вводиться правильно. Это происходит из-за того, как SecurityBundle \ DependencyInjection \ Security \ Factory настроен в рамках:

     protected function createAuthenticationSuccessHandler($container, $id, $config) { ... $successHandler = $container->setDefinition($successHandlerId, new DefinitionDecorator('security.authentication.success_handler')); $successHandler->replaceArgument(1, array_intersect_key($config, $this->defaultSuccessHandlerOptions)); ... } protected function createAuthenticationFailureHandler($container, $id, $config) { ... $failureHandler = $container->setDefinition($id, new DefinitionDecorator('security.authentication.failure_handler')); $failureHandler->replaceArgument(2, array_intersect_key($config, $this->defaultFailureHandlerOptions)); ... } 

    Он специально ищет security.authentication.success_handler и security.authentication.failure_handler , чтобы объединить параметры из вашего конфига в переданные массивы. Я уверен, что есть способ настроить что-то подобное для вашего собственного сервиса, но у меня нет еще не заглянул в нее.

    Надеюсь, это поможет.

    Вы можете легко увидеть, как в этом файле работают администраторы безопасности по умолчанию:

    продавец / Symfony / Symfony / SRC / Symfony / Bundle / SecurityBundle / Ресурсы / конфигурации / security_listeners.xml

    Например, DefaultAuthenticationSuccessHandler зарегистрирован следующим образом:

       Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler       

    Итак, наконец, мы видим, что коллекция опций по умолчанию пуста!

    options: {} выполнит задание ^^ (подумайте, что коллекция представляет {} в yaml)

    К сожалению, используя параметр success_handler в конфигурации безопасности, вы не можете предоставить пользовательский прослушиватель, который расширяет DefaultAuthenticationSuccessHandler .

    Только после устранения этой проблемы: проблема Symfony – [2.1] [Безопасность] Пользовательская аутентификацияSuccessHandler

    До тех пор самое простое решение – это то, что предложил @dmccabe :

    Globaly перезаписывает security.authentication.success_handler который отлично до тех пор, пока вам не нужно иметь несколько обработчиков для нескольких брандмауэров.

    Если вы это сделаете (начиная с этого письма), вы должны написать собственный поставщик аутентификации .

    Для лучшего решения пока прокрутите до конца этого ответа

    ОК, наконец, я получил работу так, как хотел. Проблема заключалась в том, что Symfony2 не передавал конфигурационный массив из security.yml в конструктор, когда установлен пользовательский обработчик. Так что я сделал:

    1) Я удалил объявление пользовательского обработчика из security.yml

     firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false secured_area: pattern: ^/ anonymous: ~ form_login: login_path: pkr_blog_admin_login check_path: pkr_blog_admin_login_check logout: path: pkr_blog_admin_logout target: / 

    2) AuthenticationSuccessHandler расширяет class обработчика по умолчанию, перефразирует пароль пользователя и, наконец, позволяет обработчику по умолчанию делать все остальное. В конструкторе добавлены два новых аргумента:

     #/src/Pkr/BlogUserBundle/Handler/AuthenticationSuccessHandler.php namespace Pkr\BlogUserBundle\Handler; use Doctrine\ORM\EntityManager; use Pkr\BlogUserBundle\Service\Encoder\WpTransitionalEncoder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Log\LoggerInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; use Symfony\Component\Security\Http\Authentication\Response; use Symfony\Component\Security\Http\HttpUtils; class AuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler { protected $entityManager = null; protected $logger = null; protected $encoder = null; public function __construct( HttpUtils $httpUtils, array $options, // new arguments below EntityManager $entityManager = null, # entity manager WpTransitionalEncoder $encoder = null ) { $this->entityManager = $entityManager; $this->encoder = $encoder; parent::__construct($httpUtils, $options); } /** * This is called when an interactive authentication attempt succeeds. This * is called by authentication listeners inheriting from * AbstractAuthenticationListener. * * @param Request $request * @param TokenInterface $token * * @return Response never null */ public function onAuthenticationSuccess(Request $request, TokenInterface $token) { $user = $token->getUser(); if (preg_match('^\$P\$', $user->getUserPassword())) { $newPass = $request->get('_password'); $user->setUserPassword($this->encoder->encodePassword($newPass, null)); $this->entityManager->persist($user); $this->entityManager->flush(); } return parent::onAuthenticationSuccess($request, $token); } } 

    3) добавил и изменил некоторые параметры в моих services.yml чтобы я мог использовать их в моем classе pass-компилятора:

     #/src/Pkr/BlogUserBundle/Resources/config/services.yml parameters: pkr_blog_user.wp_transitional_encoder.cost: 20 # password encoder class pkr_blog_user.wp_transitional_encoder.class: Pkr\BlogUserBundle\Service\Encoder\WpTransitionalEncoder # authentication success handler class pkr_blog_user.login_success_handler.class: Pkr\BlogUserBundle\Handler\AuthenticationSuccessHandler # entity manager service name pkr_blog_user.login_success_handler.arg.entity_manager: doctrine.orm.entity_manager # encoder service name pkr_blog_user.login_success_handler.arg.encoder: pkr_blog_user.wp_transitional_encoder services: pkr_blog_user.wp_transitional_encoder: class: "%pkr_blog_user.wp_transitional_encoder.class%" arguments: cost: "%pkr_blog_user.wp_transitional_encoder.cost%" logger: @logger pkr_blog_user.login_success_handler: class: "%pkr_blog_user.login_success_handler.class%" 

    4) создал class RehashPasswordPass компилятора RehashPasswordPass который изменяет обработчик успешности проверки подлинности по умолчанию и добавляет некоторые параметры в конструктор:

     #/src/Pkr/BlogUserBundle/DependencyInjection/Compiler/RehashPasswordPass.php namespace Pkr\BlogUserBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; class RehashPasswordPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if ($container->hasDefinition('security.authentication.success_handler')) { // definition of default success handler $def = $container->getDefinition('security.authentication.success_handler'); // changing default class $def->setClass($container->getParameter('pkr_blog_user.login_success_handler.class')); $entityMngRef = new Reference( $container->getParameter("pkr_blog_user.login_success_handler.arg.entity_manager") ); // adding entity manager as third param to constructor $def->addArgument($entityMngRef); $encoderRef = new Reference( $container->getParameter("pkr_blog_user.login_success_handler.arg.encoder") ); // adding encoder as fourth param to constructor $def->addArgument($encoderRef); } } } 

    5) добавлен переход компилятора к строителю контейнера:

     #/src/Pkr/BlogUserBundle/PkrBlogUserBundle.php namespace Pkr\BlogUserBundle; use Pkr\BlogUserBundle\DependencyInjection\Compiler\RehashPasswordPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class PkrBlogUserBundle extends Bundle { public function build(ContainerBuilder $container) { $container->addCompilerPass(new RehashPasswordPass()); } } 

    Теперь class обработчика по умолчанию был изменен, но symfony по-прежнему будет передавать конфигурацию из security.yml в конструктор плюс два новых аргумента, добавленных в passiler компилятора.

    Лучший способ

    Обработчик событий как услуга с сеттерами

     #/src/Pkr/BlogUserBundle/Resources/config/services.yml parameters: pkr_blog_user.wp_transitional_encoder.cost: 15 # password encoder class pkr_blog_user.wp_transitional_encoder.class: Pkr\BlogUserBundle\Service\Encoder\WpTransitionalEncoder # authentication success handler class pkr_blog_user.authentication_success_handler.class: Pkr\BlogUserBundle\EventHandler\AuthenticationSuccessHandler services: pkr_blog_user.wp_transitional_encoder: class: "%pkr_blog_user.wp_transitional_encoder.class%" arguments: cost: "%pkr_blog_user.wp_transitional_encoder.cost%" logger: @logger pkr_blog_user.authentication_success_handler: class: "%pkr_blog_user.authentication_success_handler.class%" calls: - [ setRequest, [ @request ]] - [ setEntityManager, [ @doctrine.orm.entity_manager ]] - [ setEncoder, [ @pkr_blog_user.wp_transitional_encoder ]] tags: - { name: kernel.event_listener, event: security.authentication.success , method: handleAuthenticationSuccess } 

    Класс обработчика событий

     # /src/Pkr/BlogUserBundle/EventHandler/AuthenticationSuccessHandler.php namespace Pkr\BlogUserBundle\EventHandler; use Doctrine\ORM\EntityManager; use Pkr\BlogUserBundle\Service\Encoder\WpTransitionalEncoder; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Event\AuthenticationEvent; class AuthenticationSuccessHandler { protected $entityManager = null; protected $encoder = null; public function setRequest(Request $request) { $this->request = $request; } public function setEntityManager(EntityManager $entityManager) { $this->entityManager = $entityManager; } public function setEncoder(WpTransitionalEncoder $encoder) { $this->encoder = $encoder; } public function handleAuthenticationSuccess(AuthenticationEvent $event) { $token = $event->getAuthenticationToken(); $user = $token->getUser(); if (preg_match('^\$P\$', $user->getUserPassword())) { $newPass = $this->request->get('_password'); $user->setUserPassword($this->encoder->encodePassword($newPass, null)); $this->entityManager->persist($user); $this->entityManager->flush(); } } } 

    И все работает, компилятор не нужен. Почему я не подумал об этом с самого начала …

    Uhh перестала работать после обновления symfony

    Теперь я получаю исключение:

    ScopeWideningInjectionException: Scope Widening Injection detected: The definition "pkr_blog_user.authentication_success_handler" references the service "request" which belongs to a narrower scope. Generally, it is safer to either move "pkr_blog_user.authentication_success_handler" to scope "request" or alternatively rely on the provider pattern by injecting the container itself, and requesting the service "request" each time it is needed. In rare, special cases however that might not be necessary, then you can set the reference to strict=false to get rid of this error.

    Кажется, что мне нужно передать полный контейнер на службу. Поэтому я изменил class services.yml и обработчик событий.

     #/src/Pkr/BlogUserBundle/Resources/config/services.yml parameters: pkr_blog_user.wp_transitional_encoder.cost: 15 # password encoder class pkr_blog_user.wp_transitional_encoder.class: Pkr\BlogUserBundle\Service\Encoder\WpTransitionalEncoder # authentication success handler class pkr_blog_user.authentication_success_handler.class: Pkr\BlogUserBundle\EventHandler\AuthenticationSuccessHandler services: pkr_blog_user.wp_transitional_encoder: class: "%pkr_blog_user.wp_transitional_encoder.class%" arguments: secure: @security.secure_random cost: "%pkr_blog_user.wp_transitional_encoder.cost%" pkr_blog_user.authentication_success_handler: class: "%pkr_blog_user.authentication_success_handler.class%" arguments: container: @service_container tags: - { name: kernel.event_listener, event: security.authentication.success , method: handleAuthenticationSuccess } 

    И обработчик событий

     # /src/Pkr/BlogUserBundle/EventHandler/AuthenticationSuccessHandler.php namespace Pkr\BlogUserBundle\EventHandler; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Security\Core\Event\AuthenticationEvent; class AuthenticationSuccessHandler { /** * @var ContainerInterface */ protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function handleAuthenticationSuccess(AuthenticationEvent $event) { $request = $this->container->get('request'); $em = $this->container->get('doctrine.orm.entity_manager'); $encoder = $this->container->get('pkr_blog_user.wp_transitional_encoder'); $token = $event->getAuthenticationToken(); $user = $token->getUser(); if (preg_match('/^\$P\$/', $user->getUserPassword())) { $newPass = $request->get('_password'); $user->setUserPassword($encoder->encodePassword($newPass, null)); $em->persist($user); $em->flush(); } } } 

    И он работает снова.

    Лучший способ до сих пор

    Решение выше было лучше всего я знал, пока @dmccabe не написал его решение .

    на самом деле лучший способ сделать это – расширить обработчик auth по умолчанию как службу

      authentication_handler: class: AppBundle\Service\AuthenticationHandler calls: [['setDoctrine', ['@doctrine']]] parent: security.authentication.success_handler public: false 

    и class AuthenticationHandler будет выглядеть так:

     class AuthenticationHandler extends DefaultAuthenticationSuccessHandler { /** * @var Registry */ private $doctrine; public function setDoctrine(Registry $doctrine) { $this->doctrine = $doctrine; } /** * This is called when an interactive authentication attempt succeeds. This * is called by authentication listeners inheriting from * AbstractAuthenticationListener. * * @param Request $request * @param TokenInterface $token * * @return Response never null */ public function onAuthenticationSuccess(Request $request, TokenInterface $token) { // do whatever you like here // ... // call default success behaviour return parent::onAuthenticationSuccess($request, $token); } } 
    Давайте будем гением компьютера.