【发布时间】: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 $user 和UsernamePasswordToken $token,所以我将dump($user); 和dump($token); 放在if ... throw 之前,终于明白发生了什么。
正如 Grzegorz Gajda 的回复所说,Symfony 代码会尝试每个提供程序,如果用户名不匹配,则会使用另一个提供程序 - 但让我失望的是它以某种方式抑制了 echo 语句如果没有匹配!因此,它似乎根本没有触及服务。但是,只要我将转储语句放入其中,就会出现回声并确认它正在打击提供者。
一旦我对一些已知良好的值进行硬编码,它就会通过身份验证并且我已登录。这证明它确实有效,尽管会产生误导,现在我只需要将实际请求连接到数据源,这一切都将是不错。
【问题讨论】: