Symfony2 - 安装了 FOS 用户包的自定义身份验证提供程序
Posted
技术标签:
【中文标题】Symfony2 - 安装了 FOS 用户包的自定义身份验证提供程序【英文标题】:Symfony2 - custom authentication provider with FOS User Bundle installed 【发布时间】:2015-11-06 04:30:46 【问题描述】:我在 Symfony2 项目中构建自定义身份验证方面需要帮助。我已经阅读了 symfony 食谱 http://symfony.com/doc/2.3/cookbook/security/custom_authentication_provider.html 并发现了许多关于自定义身份验证的问题,但是当我尝试使用 FOS 用户捆绑包时,他们没有回答我的问题。我花了很多时间研究 symfony 身份验证过程,但不明白我哪里错了。
那么我现在拥有的:
-
按照官方文档成功安装和配置 FOS 用户包。
已安装 Sonata 用户包。
已添加和配置自定义身份验证侦听器、令牌、提供程序、工厂。
之后我通常会登录,但我看到我的新身份验证提供程序没有使用,并且用户使用 FOS 的“form_login”登录。
这是我的代码:
用户实体类:
<?php
namespace Acme\UserBundle\Entity;
use Sonata\UserBundle\Entity\BaseUser as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use \Acme\BoardBundle\Entity\Card;
/**
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
* @ORM\Table(name="fos_user")
*/
class User extends BaseUser
...
protected $card;
/**
* Set card
*
* @param \Acme\BoardBundle\Entity\Card $card
* @return Card
*/
public function setCard(\Acme\BoardBundle\Entity\Card $card)
$this->card = $card;
return $this;
/**
* Get card
*
* @return \Acme\BoardBundle\Entity\Card
*/
public function getCard()
return $this->card;
用户.orm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Acme\UserBundle\Entity\User" table="fos_user">
...
<many-to-one field="card" target-entity="Acme\BoardBundle\Entity\Card" inversed-by="users">
<join-column name="card" referenced-column-name="id" />
</many-to-one>
</entity>
</doctrine-mapping>
用户实体与卡实体有关系,卡实体有两个属性:卡号和 PIN 码。以及登录后我实际上需要检查的属性。我的登录表单不仅有用户名和密码字段,还有卡号和 PIN 字段。
security.yml(我觉得我在防火墙配置中有一些错误,但我不明白出了什么问题):
providers:
fos_userbundle:
id: fos_user.user_manager
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: /admin(.*)
context: user
form_login:
provider: fos_userbundle
login_path: /admin/login
use_forward: false
check_path: /admin/login_check
failure_path: null
logout:
path: /admin/logout
anonymous: true
main:
pattern: .*
context: user
acme: true
form_login:
provider: fos_userbundle
login_path: /user/login
use_forward: false
check_path: /user/login_check
failure_path: null
always_use_default_target_path: true
default_target_path: ad_category
logout:
path: /user/logout
anonymous: true
用户令牌:
<?php
namespace Acme\UserBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class AcmeUserToken extends AbstractToken
public $userFIO;
public $cardNumber;
public $cardPIN;
public function __construct(array $roles = array())
parent::__construct($roles);
// If the user has roles, consider it authenticated
$this->setAuthenticated(count($roles) > 0);
public function getCredentials()
return '';
// поскольку токены проверяются при обработке каждом новом запросе клиента,
// нам необходимо сохранять нужные нам данные. В связи с этим “обертываем”
// унаследованные методы сериализации и десериализации.
public function serialize()
$pser = parent::serialize();
//return serialize(array($this->social, $this->hash, $this->add, $pser));
return serialize(array($pser));
public function unserialize($serialized)
//list($this->social, $this->hash, $this->add, $pser) = unserialize($serialized);
list($pser) = unserialize($serialized);
parent::unserialize($pser);
AcmeProvider.php(我的自定义身份验证提供程序):
<?php
namespace Acme\UserBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Acme\UserBundle\Security\Authentication\Token\AcmeUserToken;
class AcmeProvider implements AuthenticationProviderInterface
private $userProvider;
public function __construct(UserProviderInterface $userProvider)
$this->userProvider = $userProvider;
public function authenticate(TokenInterface $token)
$user = $this->userProvider->loadUserByUsername($token->getUsername());
if ($user)
$authenticatedToken = new AcmeUserToken($user->getRoles());
$authenticatedToken->setUser($user);
return $authenticatedToken;
throw new AuthenticationException('The Acme authentication failed.');
public function supports(TokenInterface $token)
return $token instanceof AcmeUserToken;
工厂类AcmeFactory.php:
<?php
namespace Acme\UserBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
class AcmeFactory implements SecurityFactoryInterface
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
$providerId = 'security.authentication.provider.acme.'.$id;
$container
->setDefinition($providerId, new DefinitionDecorator('acme.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider))
;
$listenerId = 'security.authentication.listener.acme.'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('acme.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
public function getPosition()
//return 'pre_auth';
return 'form';
public function getKey()
return 'acme';
public function addConfiguration(NodeDefinition $node)
config.yml中user provider和listner的配置:
services:
acme.security.authentication.provider:
class: Acme\UserBundle\Security\Authentication\Provider\AcmeProvider
abstract: true
arguments: ['']
public: false
security.authentication.listener.abstract:
tags:
- name: 'monolog.logger', channel: 'security'
arguments: [@security.context, @security.authentication.manager, @security.authentication.session_strategy, @security.http_utils, "knetik",@security.authentication.success_handler, @security.authentication.failure_handler, , @logger, @event_dispatcher]
class: Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener
# override application level success handler and re-route back
security.authentication.success_handler:
class: Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler
arguments: ["@security.http_utils", ]
tags:
- name: 'monolog.logger', channel: 'security'
# override application level failure handler and re-route back
security.authentication.failure_handler:
class: Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler
arguments: ["@http_kernel", "@security.http_utils", , "@logger"]
tags:
- name: 'monolog.logger', channel: 'security'
yamogu.security.authentication.listener:
class: Acme\UserBundle\Security\Authentication\Firewall\AcmeListener
parent: security.authentication.listener.abstract
abstract: true
arguments: ["@security.context", "@security.authentication.manager"]
public: false
如果您需要附加代码,我会将其添加到问题中。 任何帮助将不胜感激!
授权后dev.log上的链接:https://www.dropbox.com/s/5uot2qofmqjwvmk/dev.log?dl=0
【问题讨论】:
【参考方案1】:我找到了解决问题的方法,但我采取了另一种方式。 我为 form_login 定义了成功认证处理程序和失败处理程序,并将我的逻辑放在这里。如果用户输入错误的用户名但正确的卡号和密码,我会在故障处理程序中手动注册用户。反之亦然,如果用户输入了正确的用户名,但输入了错误的卡号和密码,那么我在成功验证失败时拒绝他的登录并手动注销他。
查看我的源代码
平安安全.yml:
security:
firewalls:
...
main:
pattern: .*
context: user
form_login:
provider: fos_userbundle
login_path: /user/login
use_forward: false
check_path: /user/login_check
failure_path: null
always_use_default_target_path: true
default_target_path: ad_category
success_handler: authentication_success_handler
failure_handler: authentication_failure_handler
logout:
path: /user/logout
anonymous: true
config.yml:
services:
authentication_success_handler:
class: Yamogu\UserBundle\Handler\AuthenticationSuccessHandler
arguments: [@router, @doctrine.orm.entity_manager, @security.context]
authentication_failure_handler:
class: Yamogu\UserBundle\Handler\AuthenticationFailureHandler
arguments: [@router, @doctrine.orm.entity_manager, @security.context, @event_dispatcher]
AuthenticationSuccessHandler.php:
namespace Acme\UserBundle\Handler;
Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Router;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\BoardBundle\Entity\Card;
use Symfony\Component\Security\Core\SecurityContext;
class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface
protected $router;
private $om;
private $securityContext;
public function __construct(Router $router, ObjectManager $om, SecurityContext $securityContext)
$this->router = $router;
$this->om = $om;
$this->securityContext = $securityContext;
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
$fosUser = $this->securityContext->getToken()->getUser();
if($fosUser->getCard())
$card = $fosUser->getCard()->getNumber();
$pin = $fosUser->getCard()->getPin();
if($card == $request->get('card') && $pin == $request->get('pin'))
// if Log out the user he inputs wrong card
$loginName = $request->get('firstname');
$fosUserFirstName = $fosUser->getFirstname();
if($loginName && $loginName != $fosUserFirstName)
$fosUser->setFirstname($loginName);
$this->om->flush();
return new RedirectResponse($this->router->generate("ad_category"));
$this->securityContext->setToken(null);
$request->getSession()->invalidate();
$request->getSession()->getFlashBag()->set('acme_login_error', 'Error!');
return new RedirectResponse($this->router->generate("fos_user_security_login"));
?>
AuthenticationFailureHandler.php:
<?php
namespace Acme\UserBundle\Handler;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Router;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Acme\BoardBundle\Entity\Card;
use Acme\UserBundle\Entity\User as YamUser;
class AuthenticationFailureHandler implements AuthenticationFailureHandlerInterface
protected $router;
private $om;
private $securityContext;
private $eventDispatcher;
public function __construct(Router $router, ObjectManager $om, SecurityContext $securityContext, EventDispatcher $eventDispatcher)
$this->router = $router;
$this->om = $om;
$this->securityContext = $securityContext;
$this->eventDispatcher = $eventDispatcher;
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
if($request->get('firstname') !== null && $request->get('_username') && $request->get('_password') !== null && $request->get('card') !== null && $request->get('pin') !== null)
$loginName = $request->get('firstname');
$username = $request->get('_username');
$passw = $request->get('_password');
$loginCard = $request->get('card');
$loginPin = $request->get('pin');
$card = $this->om->getRepository('AcmeBoardBundle:Card')
->findOneBy(array("number" => $loginCard, "pin" => $loginPin));
// If there is the requested card in the DB create a new user and log in him at the moment
if($card)
// Create a new user for this card, log in him and redirect to the board
$entity = new YamUser();
$entity->setCard($card);
$entity->setFirstname($loginName);
$entity->setUsername($username);
$entity->setPlainPassword($passw);
$entity->setEmail($username);
$entity->setEnabled(true);
$this->om->persist($entity);
$this->om->flush();
$token = new UsernamePasswordToken($entity, null, "main", $entity->getRoles());
$this->securityContext->setToken($token); //now the user is logged in
//now dispatch the login event
$event = new InteractiveLoginEvent($request, $token);
$this->eventDispatcher->dispatch("security.interactive_login", $event);
return new RedirectResponse($this->router->generate("ad_category"));
$this->securityContext->setToken(null);
$request->getSession()->invalidate();
$request->getSession()->getFlashBag()->set('acme_login_error', 'Error!');
return new RedirectResponse($this->router->generate("fos_user_security_login"));
?>
正如我所见,这不是解决任务的最佳方法,但它对我有用。如果有人对我的解决方案有更好的解决方案或修复,请在此处添加!
【讨论】:
【参考方案2】:您必须让您的安全上下文知道您在 bundle 类中的工厂。在你的 bundle 类中这样做:
class UserBundle extends Bundle
public function build(ContainerBuilder $container)
parent::build($container);
$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new AcmeFactory());
public function getParent()
return 'FOSUserBundle';
[编辑] Symfony 中的安全层很难理解!我建议你关注这个blog post 来了解 Symfony 的安全性。
【讨论】:
感谢您的回复!我已经在构建方法中有工厂声明,但我没有函数 getParent()。我会尽快检查并发布结果。 我认为扩展 FOSUserBundle 不会解决问题!。你检查你的日志文件了吗? 是的,你是对的。当我添加返回'FOSUserBundle'的getParent()方法时,我得到LogicException:Bundle“FOSUserBundle”直接由两个包“YamoguUserBundle”和“SonataUserBundle”扩展。所以我将 'FOSUserBundle' 更改为 'SonataUserBundle' 并修复了异常。 但是认证的问题依然存在。顺便说一句,在成功授权后,我在 symfony 调试面板中看到 Token 类是 UsernamePasswordToken。这不是我的用户令牌类。 日志文件是什么意思?我检查了 PHP 错误日志文件,但在这种情况下没有错误。以上是关于Symfony2 - 安装了 FOS 用户包的自定义身份验证提供程序的主要内容,如果未能解决你的问题,请参考以下文章
symfony2:奏鸣曲 userbundle + fos userbundle + mongodb => 错误“sonata.user.mongodb.user_manager”不存在