Symfony2 - 安装了 FOS 用户包的自定义身份验证提供程序

     2023-02-16     28

关键词:

【中文标题】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 错误日志文件,但在这种情况下没有错误。

FOS UserBundle 无法登录

...述】:我又遇到了关于我的UserBundle的另一个问题:通过Symfony2安装和配置FOS包时一切都很完美,它甚至让我创建了2个正确插入到我的数据库中的用户。但是,每次我尝试登录其中任何一个帐户时,都会收到以下错误Warning:Erroneous... 查看详情

如何覆盖 FOS 用户捆绑表单标签

...【发布时间】:2012-11-0812:39:46【问题描述】:我很难覆盖Symfony2的FOS用户包使用的标签。我已经覆盖了Form类,但是没有像“setOption”这样的元素的选项,只有getters。我可以删除一个元素,然后用适当的标签再次添加它,但这似... 查看详情

Symfony,FOS 用户:使用空输入密码编辑用户

...ymfony,FOS用户:使用空输入密码编辑用户【英文标题】:Symfony2,FOSuser:Edituserwithemptyinputpassword【发布时间】:2015-02-1914:53:45【问题描述】:我正在使用我自己的扩展fos_user的实体。我实际上在2个不同的表中扩展了用户实体(扩展... 查看详情

FOS 用户更新错误

...间】:2017-01-2023:55:53【问题描述】:我正在使用Sonata进行Symfony2,8项目。我发现用户是用FOS管理的:namespaceVoyage\\AdminBundle\\Admin;useSonata\\AdminBundle\\Admin\\Admin;useSonata\\AdminBundle\\Form\\FormMapper 查看详情

使用 FOS 用户包编辑用户配置文件

...016-05-1902:45:10【问题描述】:我正在使用带有FOSUserBundle的symfony2,问题是添加用户时,当前登录的用户配置文件将被当前添加的用户替换,然后当我尝试编辑其他用户配置文件时,唯一可编辑的是当前登录的用户,从FOSUserBundle继... 查看详情

Symfony2 多 FosUserBundle

】Symfony2多FosUserBundle【英文标题】:Symfony2multiFosUserBundle【发布时间】:2012-04-0512:51:26【问题描述】:我使用FosUserBundle进行登录/注册操作我希望有两个或多个用户表作为用户,BackUser,...我还需要两个不同的防火墙:firewalls:back:... 查看详情

如何自定义 FOS UserBundle URL

...leURLs【发布时间】:2013-11-0516:05:44【问题描述】:我已经安装了FOSUserBundle,我想将网址自定义为/account/login、/account/register、/account/logout,而不是/login、/register、/logout我知道我可以修改捆绑包的路由配置,但这似乎不是 查看详情

Symfony 2.7 / 3 - Doctrine:您请求了一个不存在的服务“fos_user.doctrine_registry”

】Symfony2.7/3-Doctrine:您请求了一个不存在的服务“fos_user.doctrine_registry”【英文标题】:Symfony2.7/3-Doctrine:Youhaverequestedanon-existentservice"fos_user.doctrine_registry"【发布时间】:2016-05-0401:15:53【问题描述】:今天做作曲家更新突... 查看详情

symfony2:奏鸣曲 userbundle + fos userbundle + mongodb => 错误“sonata.user.mongodb.user_manager”不存在

】symfony2:奏鸣曲userbundle+fosuserbundle+mongodb=>错误“sonata.user.mongodb.user_manager”不存在【英文标题】:symfony2:sonatauserbundle+fosuserbundle+mongodb=>error"sonata.user.mongodb.user_manager"doesnotexist【发布时间】:2014 查看详情

FOS 捆绑用户 - 多个角色

】FOS捆绑用户-多个角色【英文标题】:FOSBundleUser-multipleroles【发布时间】:2018-04-0316:44:03【问题描述】:我正在探索FOSBundleUser的可能性。感谢Knpuniversity(https://knpuniversity.com/screencast/fosuserbundle/roles-canonical-fields)我发现了这个用于... 查看详情

symfony2:使用 fosuserbundle 登录后使用引用

】symfony2:使用fosuserbundle登录后使用引用【英文标题】:symfony2:usingrefererafterloginwithfosuserbundle【发布时间】:2014-01-1916:15:44【问题描述】:我在登录成功后使用fosuserbundle将用户重定向到引用者时遇到问题app/config/security.ymlsecurity:... 查看详情

Sonata + Fos_user - 如何仅显示与用户相关的实体?

】Sonata+Fos_user-如何仅显示与用户相关的实体?【英文标题】:Sonata+Fos_user-Howtodisplayonlyentitiesrelatedtotheuser?【发布时间】:2017-10-0520:53:10【问题描述】:我的用户是场地管理员。我希望他们能够管理他们的地方和在这些地方发生... 查看详情

Symfony2 - 用户、捆绑包和控制器 [关闭]

】Symfony2-用户、捆绑包和控制器[关闭]【英文标题】:Symfony2-Users,BundlesandControllers[closed]【发布时间】:2014-02-1720:29:52【问题描述】:大家好,我是Symfony的新手,我仍在探索这个框架的全部潜力。我的问题是是否可以在Symfony2上... 查看详情

Symfony2 中的 Oauth2

】Symfony2中的Oauth2【英文标题】:Oauth2inSymfony2【发布时间】:2011-11-2223:52:38【问题描述】:我想在我的Symfony2项目中使用一些API,例如foursquare。问题是,我在OAuth上没有找到关于Twitter/Facebook的2个很棒的FOS捆绑包,而不是任何东... 查看详情

Symfony2/FOSUserBundle - 多个包的路由问题

】Symfony2/FOSUserBundle-多个包的路由问题【英文标题】:Symfony2/FOSUserBundle-Routeissueswithmultiplebundles【发布时间】:2013-04-2303:53:56【问题描述】:类似于this问题,我需要为我的站点的管理员和前端包单独登录。admin实际上是一个单独... 查看详情

Symfony2:--重新安装问题

】Symfony2:--重新安装问题【英文标题】:Symfony2:--reinstallissue【发布时间】:2012-05-2405:12:28【问题描述】:我一直在尝试为Symfony2安装Buzz。我将它添加到deps文件中,并尝试更新我的供应商:phpbin/vendorsupdate该命令只是告诉我我已... 查看详情

更改 FOS 用户连接属性

】更改FOS用户连接属性【英文标题】:ChangetheFOSusersconnectionproperty【发布时间】:2020-06-1804:56:13【问题描述】:我和symfony和jwtbundle的朋友连接有问题,默认使用用户名连接,我只需要使用email。我首先尝试更改secutiy.yaml中的属性... 查看详情

Symfony2 用户权限管理

】Symfony2用户权限管理【英文标题】:Symfony2Userpermissionmanagment【发布时间】:2016-06-2805:16:03【问题描述】:我来找你是因为我有点担心Symfony2的用户管理和他们的权限。让我解释一下:我设置了FOSUserBundle:我现在想做的是权限管... 查看详情