【问题标题】:How to use a custom user provider with Symfony 3 authentication如何在 Symfony 3 身份验证中使用自定义用户提供程序
【发布时间】:2018-02-23 15:57:12
【问题描述】:

我几天来一直在尝试让 Symfony 3 身份验证与自定义“用户提供程序”一起工作。我在 Symfony 文档中遵循了本教程:https://symfony.com/doc/current/security/custom_provider.html,其中显示了从 Web 服务获取用户数据的示例。

我已经根据该页面(以及 Symfo 文档中的其他页面)完成了所有工作,并尝试了许多细节变化。 Symfony 身份验证从不调用我的 WebserviceUserProvider。我的 WebserviceUserProvider 根据需要实现 loadUserByUsername($username),但该方法中的 echo 语句永远不会执行。

这里是创建或更改的文件。 WebserviceUser:

namespace AppBundle\Security\User;
/*
 * src/AppBundle/Security/User/WebserviceUser.php
 */
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;

class WebserviceUser implements UserInterface, EquatableInterface, \Serializable
{
    private $id = 0;
    private $username;
    private $password;
    private $email;
    private $isActive;
    private $roles;

    public function __construct()
    {
        // DEGUBBING
        echo "creating new WebserviceUser";
    }


    // ----------------- getters

    public function getId()         { return $this->id;    }
    public function getEmail()      { return $this->email;    }
    public function isActive()      { return $this->isActive;    }
    // next four are required by the interface
    public function getRoles()      { return $this->roles;    }
    public function getPassword()   { return $this->password;    }
    public function getSalt()       { return null;    }
    public function getUsername()   { return $this->username;    }


    // ------------------------------ setters

    public function setId(int $i)           { $this->id = $i;    }
    public function setEmail(string $s)     { $this->email = $s;    }
    public function setIsActive(bool $b)    { $this->isActive = $b; }
    public function setRoles(array $a)      { $this->roles = $a;    }
    public function setPassword(string $s)  { $this->password = $s;    }
    public function setSalt(string $s)      { $this->salt = $s; }
    public function setUsername(string $s)  { $this->username = $s;    }


    // ----------------------------- misc.

    // required by interface
    public function eraseCredentials()
    {
    }

    /** @see \Serializable::serialize() */
    public function serialize()
    {
        // copied exactly from tuto
        // ...

    /** @see \Serializable::unserialize() */
    public function unserialize($serialized)
    {
        // copied exactly from tuto
        // ...

    public function isEqualTo(UserInterface $user)
    {
        // copied exactly from tuto
        // ...

}

WebserviceUserProvider

namespace AppBundle\Security\User;
/*
 * src/AppBundle/Security/User/WebserviceUserProvider.php
 */
use AppBundle\Security\User\WebserviceUser;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class WebserviceUserProvider implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        // Symfo docs sample: "make a call to your webservice here
        // $userData = ... 
        // "

        echo "This is WebserviceUserProvider::loadUserByUsername()";

        $user =  new WebserviceUser();
        $user->setUsername('testname');
        $user->setPassword('testpass');
        $user->setEmail('test@none.com');
        $user->setIsActive(true);
        return $user;
    }

    public function refreshUser(UserInterface $user)
    {
        if(!$user instanceof WebserviceUser){
            throw new UnsupportedUserException(
                'Class is ' . get_class($user) . ', must be WebserviceUser');
        }
        return $this->loadUserByUsername($user->getUsername());
    }

    public function supportsClass($class)
    {
        return WebserviceUser::class === $class;
    }
}

我的security.yml

# app/config/security.yml
# http://symfony.com/doc/current/security.html
security:

    encoders:
        # BCrypt encoder
        ### Acme\DemoBundle\Entity\User4:
        Symfony\Component\Security\Core\User\User:
            algorithm:            bcrypt
            cost:                 13
        AppBundle\Security\User\WebserviceUser:
            algorithm:            bcrypt
            cost:                 13
        AppBundle\Entity\UserRegister:
            algorithm:            bcrypt
            cost:                 13

    providers:
        #in_memory:
        #    memory: ~

        webservice:
            id: AppBundle\Security\User\WebserviceUserProvider

    //hide_user_not_found: false

    firewalls:
        # disables authentication for assets and the profiler,
        # adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            pattern:    ^/
            #http_basic: ~

            form_login:
                login_path: login
                check_path: login

            anonymous: ~
            # activate different ways to authenticate

    access_control:
        # Order matters in this list. Each path controls all under it and later
        # more specific does not overrule - so go from more specific to less.
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/survey, roles: ROLE_ORG_USER }
        - { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }

// ...

access_control 部分和表单页面都按预期工作。登录尝试总是会收到消息Invalid credentials(应该如此)。查看控制器中$authUtils->getLastAuthenticationError() 的输出会得到一个堆栈跟踪,其中不包含对“webservice”的任何引用。

我尝试了上述所有方法的许多不同调整,查找了许多关于 SO 的 Q&A 和许多方法,但没有发现任何有用的东西。如您所知,以上内容并不完整,但我只需要让 Symfony 使用提供程序,然后我就可以处理其余的事情。

这显然对其他人有用,但我不知道他们在做什么不同。我错过了什么?

编辑:堆栈跟踪是这样的:

error: Symfony\Component\Security\Core\Exception\BadCredentialsException: The presented password is invalid. in /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php:67 Stack trace:
#0 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php(144): session_start()
#1 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php(282): Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->start()
#2 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/Session/Session.php(259): Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->getBag('attributes')
#3 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/Session/Session.php(87): Symfony\Component\HttpFoundation\Session\Session->getAttributeBag()
#4 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/ContextListener.php(83): Symfony\Component\HttpFoundation\Session\Session->get('_security_main')
#5 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall.php(69): Symfony\Component\Security\Http\Firewall\ContextListener->handle(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#6 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Bundle/SecurityBundle/EventListener/FirewallListener.php(48): Symfony\Component\Security\Http\Firewall->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#7 [internal function]: Symfony\Bundle\SecurityBundle\EventListener\FirewallListener->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher))
#8 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php(104): call_user_func(Array, Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher))
#9 [internal function]: Symfony\Component\EventDispatcher\Debug\WrappedListener->__invoke(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher))
#10 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/EventDispatcher.php(212): call_user_func(Object(Symfony\Component\EventDispatcher\Debug\WrappedListener), Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher))
#11 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/EventDispatcher.php(44): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#12 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php(146): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#13 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php(129): Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#14 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php(68): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1)
#15 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php(171): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#16 /srv/www/site1/web/app_dev.php(28): Symfony\Component\HttpKernel\Kernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#17 /srv/www/site1/vendor/symfony/symfony/src/Symfony/Bundle/WebServerBundle/Resources/router.php(42): require('/srv/www/site1...')
#18 {main}

更新:
跟踪中的最后一个(第一个列出的)项目看起来最有可能,即vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php。而DaoAuthenticationProvider::checkAuthentication()(其中 l. 67 是)接受参数UserInterface $userUsernamePasswordToken $token,所以我将dump($user);dump($token); 放在if ... throw 之前,终于明白发生了什么。

正如 Grzegorz Gajda 的回复所说,Symfony 代码会尝试每个提供程序,如果用户名不匹配,则会使用另一个提供程序 - 但让我失望的是它以某种方式抑制了 echo 语句如果没有匹配!因此,它似乎根本没有触及服务。但是,只要我将转储语句放入其中,就会出现回声并确认它正在打击提供者。

一旦我对一些已知良好的值进行硬编码,它就会通过身份验证并且我已登录。这证明它确实有效,尽管会产生误导,现在我只需要将实际请求连接到数据源,这一切都将是不错。

【问题讨论】:

    标签: symfony authentication


    【解决方案1】:

    基本 Symfony 的 Symfony\Component\Security\Core\User\ChainUserProvider 获取所有已定义的提供者(您的和默认的)并迭代它们直到找到至少一个匹配用户名 (source) 的用户。

    Symfony 的安全组件

    也许在您的数据库中,您有用户名为 testname 的用户,并且您的提供程序没有被解雇。正如您在line 62 中看到的,默认ChainUserProvider 抛出异常消息There is no user with name "%s".,而不是Invalid credentials

    好的,但是AuthenticationException::getMessageKey() 怎么样?它用于显示翻译后的消息,因此我们可以依赖异常消息。现在是调查证券异常的时候了。 UsernameNotFoundException 包含消息 'Username could not be found.'BadCredentialsException 包含 'Invalid credentials.'。当任何提供者没有用户名为testname 的用户时,我们的提供者应该返回UsernameNotFoundException

    解决方案

    您可以为整个提供商 (documentation) 选择您的唯一一个提供商,甚至可以创建自己的链提供商并使用它。

    【讨论】:

    • “无效凭据”是由于树枝行:{{ error.messageKey|trans(error.messageData, 'security') }} 输出该短语而不是实际跟踪。真正的消息有“BadCredentialsException”。 “testname”是伪造的,预计不会起作用 - 我只是想了解为什么 echo 语句永远不会执行。缺少它对我说,从未调用过提供者。如您所见,Web 服务是列出的唯一提供者。
    • 尝试在身份验证期间查找您的堆栈跟踪,并找到 Guard 检查您的密码的内容,并在dump() 那里提供提供者列表。
    猜你喜欢
    • 2012-02-22
    • 1970-01-01
    • 2015-08-24
    • 1970-01-01
    • 2017-01-04
    • 2012-04-05
    • 1970-01-01
    • 2015-06-29
    • 2012-11-09
    相关资源
    最近更新 更多