我想在身份验证成功后更改默认身份验证过程。我创建了一个在身份验证成功之后和重定向之前调用的服务。
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
}
}
in 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服务添加到我的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
答案 0 :(得分:44)
如果您只为应用程序定义了一个成功/失败处理程序,则执行此操作的方法会稍微简单一些。您可以覆盖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
<?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
,以便将配置中的选项合并到传入的数组中。我确信有一种方法可以为您自己的服务设置类似的东西,但我尚未调查过。
希望有所帮助。
答案 1 :(得分:2)
您可以在此文件中轻松查看默认安全侦听器的管理方式:
供应商/ symfony的/ symfony的/ SRC / Symfony的/捆绑/ SecurityBundle /资源/配置/ 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)
答案 2 :(得分:2)
好的,我终于以我想要的方式工作了。问题是,当设置自定义处理程序时,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
传递给构造函数以及编译器传递添加的两个新参数。
事件处理程序作为具有setter的服务
#/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();
}
}
}
它一切正常,不需要编译器传递。为什么我从一开始就没有想到......
现在我得到例外:
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写下他的solution。
答案 3 :(得分:1)
不幸的是,通过在安全配置中使用success_handler
选项,您无法提供扩展DefaultAuthenticationSuccessHandler
的自定义侦听器。
直到此问题得到解决:Symfony issue - [2.1][Security] Custom AuthenticationSuccessHandler
在此之前,最简单的解决方案是@dmccabe
建议的内容:
Globaly覆盖security.authentication.success_handler
,只要您不需要为多个防火墙提供多个处理程序,就可以了。{/ p>
如果你这样做(截至撰写本文时),你必须write your own Authentication Provider。
答案 4 :(得分:0)
实际上,最好的方法是将默认的auth处理程序扩展为服务
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);
}
}