要获得迄今为止最好的解决方案,请滚动到此答案的底部
好的,我终于让它以我想要的方式工作。问题是 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
扩展默认处理程序类,重新哈希用户密码,最后让默认处理程序完成其余工作。在构造函数中添加了两个新参数:
#/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
以便我可以在我的编译器传递类中使用它们:
#/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) 创建了一个编译器传递类,该类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());
}
}
现在默认处理程序类已更改,但 symfony 仍会将配置从构造函数传递security.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:
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 写下他的解决方案之前,我知道上面的解决方案是最好的。