27

認証成功直後にデフォルトの認証プロセスを変更したい。認証成功後、リダイレクト前に呼び出されるサービスを作成しました。

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 で設定) を拡張クラス コンストラクターに追加する方法がわからないことです。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 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 サービスを my に追加するのは非常に簡単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:
            httputils: @security.http_utils
            options: [] #WHAT TO ADD HERE ?
            entity_manager: @doctrine.orm.entity_manager
            logger: @logger
            encoder: @pkr_blog_user.wp_transitional_encoder
4

5 に答える 5

46

アプリケーションに成功/失敗ハンドラーが 1 つしか定義されていない場合は、これを行うための少し簡単な方法があります。success_handlerandの新しいサービスを定義するのではなく、代わりにandfailure_handlerをオーバーライドできます。security.authentication.success_handlersecurity.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

<?php
namespace StatSidekick\UserBundle\Handler;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
use Symfony\Component\Security\Http\HttpUtils;

class AuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler {

    public function __construct( HttpUtils $httpUtils, array $options ) {
        parent::__construct( $httpUtils, $options );
    }

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

AuthenticationFailureHandler.php

<?php
namespace StatSidekick\UserBundle\Handler;

use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler;
use Symfony\Component\Security\Http\HttpUtils;

class AuthenticationFailureHandler extends DefaultAuthenticationFailureHandler {

    public function __construct( HttpKernelInterface $httpKernel, HttpUtils $httpUtils, array $options, LoggerInterface $logger = null ) {
        parent::__construct( $httpKernel, $httpUtils, $options, $logger );
    }

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

私の場合は、AJAX を使用して認証しようとしたときに JSON 応答を取得できるように設定しようとしていただけでしたが、原理は同じです。

このアプローチの利点は、追加の作業をしなくても、デフォルト ハンドラーに通常渡されるすべてのオプションが正しく挿入されることです。これは、フレームワークでの 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ます。独自のサービスに同様のものをセットアップする方法があると確信していますが、まだ調べていません。

それが役立つことを願っています。

于 2013-06-08T16:22:14.093 に答える
2

これまでの最良の解決策については、この回答の一番下までスクロールしてください

OK、ついに思い通りに動作するようになりました。security.yml問題は、カスタム ハンドラーが設定されているときに、Symfony2 が構成配列をコンストラクターに渡していないことでした。だから私がしたことは:

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デフォルト ハンドラー クラスを拡張し、ユーザー パスワードを再ハッシュして、最後にデフォルト ハンドラーに残りを任せます。コンストラクターに 2 つの新しい引数が追加されました。

#/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);
    }
}

services.yml3)コンパイラパスクラスで使用できるように、いくつかのパラメータを追加および変更しました。

#/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%"

RehashPasswordPass4)デフォルトの認証成功ハンドラーを変更し、いくつかのパラメーターをコンストラクターに追加するコンパイラー パス クラスを作成しました。

#/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());
    }
}

デフォルトのハンドラー クラスが変更されましたが、symfony は構成security.ymlをコンストラクターに渡し、さらにコンパイラー パスによって追加された 2 つの新しい引数を渡します。

より良い方法

セッターを備えたサービスとしてのイベントハンドラー

#/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();
        }
    }

}

そして、それはすべて機能しており、コンパイラ パスは必要ありません。なぜ最初から考えなかったのか…

うーん、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.

フルコンテナをサービスに渡す必要があるようです。そこで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 が彼の解決策を書くまで私が知っていた最善の方法でした。

于 2013-04-13T13:38:54.417 に答える
2

このファイルで、デフォルトのセキュリティ リスナーがどのように管理されているかを簡単に確認できます。

ベンダー/symfony/symfony/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml

たとえば、 DefaultAuthenticationSuccessHandler は次のように登録されます。

    <!-- Parameter -->

    <parameter key="security.authentication.success_handler.class">Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler</parameter>

    <!-- Service -->

    <service id="security.authentication.success_handler" class="%security.authentication.success_handler.class%" abstract="true" public="false">
        <argument type="service" id="security.http_utils" />
        <argument type="collection" /> <!-- Options -->
    </service>

最後に、オプション コレクションがデフォルトで空であることを確認できます。

options: {}^^ (コレクションは yaml の {} で表されると考えてください)

于 2013-04-10T07:46:18.490 に答える
0

実際にこれを行う最善の方法は、デフォルトの認証ハンドラーをサービスとして拡張することです

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

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);
    }
}
于 2016-08-08T23:34:20.147 に答える