问题描述
我试图为我的登录表单创建一个身份验证器,但是由于某些不清楚的原因,我始终没有登录.
I tried to create an authenticator for my login form, but I always am unlogged for some unclear reason.
[2016-10-05 18:54:53] security.INFO: Guard authentication successful! {"token":"[object] (Symfony\\Component\\Security\\Guard\\Token\\PostAuthenticationGuardToken: PostAuthenticationGuardToken(user=\"[email protected]\", authenticated=true, roles=\"ROLE_USER\"))","authenticator":"AppBundle\\Security\\Authenticator\\FormLoginAuthenticator"} []
[2016-10-05 18:54:54] security.INFO: An AuthenticationException was thrown; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationExpiredException(code: 0): at /space/products/insurance/vendor/symfony/symfony/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php:86)"} []
[2016-10-05 18:54:54] security.INFO: The security token was removed due to an AccountStatusException. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationExpiredException(code: 0): at /space/products/insurance/vendor/symfony/symfony/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php:86)"} []
我不理解此"AuthenticationExpiredException",因为我没有无状态的东西,也没有以任何方式在我的应用程序中到期.
I don't understand this "AuthenticationExpiredException" as I have nothing stateless, nor any expiration in any way nowhere in my app.
这个问题和任何人说话吗?
Does this issue speak to anyone?
编辑1
几个小时后,由于Twig中的{{ is_granted('ROLE_USER') }}
,我似乎没有登录.还是不明白为什么.
After a bunch of hours, it looks like I am unlogged because of the {{ is_granted('ROLE_USER') }}
in Twig. Don't see why anyway.
编辑2
如果我在onAuthenticationSuccess身份验证器的方法authenticated = true
上转储()我的安全令牌.
If I dump() my security token on the onAuthenticationSuccess authenticator's method, authenticated = true
.
但是,如果我在重定向后或访问新页面时转储了我的安全令牌,则为'authenticated' = false
.
But, If I dump() my security token after a redirect or when accessing a new page, 'authenticated' = false
.
为什么我的身份验证没有存储.
Why the hell my authentication isn't stored.
security:
encoders:
AppBundle\Security\User\Member:
algorithm: bcrypt
cost: 12
providers:
members:
id: app.provider.member
role_hierarchy:
ROLE_ADMIN: "ROLE_USER"
firewalls:
dev:
pattern: "^/(_(profiler|wdt|error)|css|images|js)/"
security: false
main:
pattern: "^/"
anonymous: ~
logout: ~
guard:
authenticators:
- app.authenticator.form_login
access_control:
- { path: "^/connect", role: "IS_AUTHENTICATED_ANONYMOUSLY" }
- { path: "^/register", role: "IS_AUTHENTICATED_ANONYMOUSLY" }
- { path: "^/admin", role: "ROLE_ADMIN" }
- { path: "^/user", role: "ROLE_USER" }
- { path: "^/logout", role: "ROLE_USER" }
<?php
namespace AppBundle\Controller;
use AppBundle\Base\BaseController;
use AppBundle\Form\Type\ConnectType;
use AppBundle\Security\User\Member;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
class SecurityController extends BaseController
{
/**
* @Route("/connect", name="security_connect")
* @Template()
*/
public function connectAction(Request $request)
{
$connectForm = $this
->createForm(ConnectType::class)
->handleRequest($request)
;
return [
'connect' => $connectForm->createView(),
];
}
}
<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Validator\Constraints;
use EWZ\Bundle\RecaptchaBundle\Form\Type\EWZRecaptchaType;
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\IsTrue as RecaptchaTrue;
class ConnectType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', Type\EmailType::class, [
'label' => 'Your email',
'required' => true,
'constraints' => [
new Constraints\Length(['min' => 8])
],
])
->add('password', Type\PasswordType::class, [
'label' => 'Your password',
'constraints' => new Constraints\Length(['min' => 8, 'max' => 4096]), /* CVE-2013-5750 */
])
->add('recaptcha', EWZRecaptchaType::class, [
'label' => 'Please tick the checkbox below',
'constraints' => [
new RecaptchaTrue()
],
])
->add('submit', Type\SubmitType::class, [
'label' => 'Connect',
])
;
}
}
<?php
namespace AppBundle\Security\Authenticator;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use AppBundle\Form\Type\ConnectType;
class FormLoginAuthenticator extends AbstractFormLoginAuthenticator
{
private $container; // ¯\_(ツ)_/¯
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getCredentials(Request $request)
{
if ($request->getPathInfo() !== '/connect') {
return null;
}
$connectForm = $this
->container
->get('form.factory')
->create(ConnectType::class)
->handleRequest($request)
;
if ($connectForm->isValid()) {
$data = $connectForm->getData();
return [
'username' => $data['email'],
'password' => $data['password'],
];
}
return null;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
return $userProvider->loadUserByUsername($credentials['username']);
}
public function checkCredentials($credentials, UserInterface $user)
{
$isValid = $this
->container
->get('security.password_encoder')
->isPasswordValid($user, $credentials['password'])
;
if (!$isValid) {
throw new BadCredentialsException();
}
return true;
}
protected function getLoginUrl()
{
return $this
->container
->get('router')
->generate('security_connect')
;
}
protected function getDefaultSuccessRedirectUrl()
{
return $this
->container
->get('router')
->generate('home')
;
}
}
<?php
namespace AppBundle\Security\Provider;
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;
use AppBundle\Security\User\Member;
use Api\Gateway\RequestResponse\RequestResponseHandlerInterface;
use Api\Business\InsuranceWebsite\Action\GetInsuranceMember\GetInsuranceMemberRequest;
use Api\Gateway\Exception\NoResultException;
class MemberProvider implements UserProviderInterface
{
protected $gateway;
public function __construct(RequestResponseHandlerInterface $gateway)
{
$this->gateway = $gateway;
}
public function loadUserByUsername($username)
{
try {
$response = $this->gateway->handle(
new GetInsuranceMemberRequest($username)
);
} catch (NoResultException $ex) {
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
$member = new Member();
$member->setId($response->getId());
$member->setUsername($response->getEmail());
$member->setPassword($response->getPassword());
$member->setCompanyId($response->getCompanyId());
$member->setFirstname($response->getFirstname());
$member->setLastname($response->getLastname());
$member->setIsManager($response->isManager());
$member->setIsEnabled($response->isEnabled());
return $member;
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof Member) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === Member::class;
}
}
<?php
namespace AppBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
class Member implements UserInterface
{
private $id;
private $username;
private $password;
private $companyId;
private $firstname;
private $lastname;
private $isManager;
private $isEnabled;
private $roles = ['ROLE_USER'];
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
return $this;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
return $this;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
return $this;
}
public function getCompanyId()
{
return $this->companyId;
}
public function setCompanyId($companyId)
{
$this->companyId = $companyId;
return $this;
}
public function getFirstname()
{
return $this->firstname;
}
public function setFirstname($firstname)
{
$this->firstname = $firstname;
return $this;
}
public function getLastname()
{
return $this->lastname;
}
public function setLastname($lastname)
{
$this->lastname = $lastname;
return $this;
}
public function isManager()
{
return $this->isManager;
}
public function setIsManager($isManager)
{
$this->isManager = $isManager;
return $this;
}
public function IsEnabled()
{
return $this->isEnabled;
}
public function setIsEnabled($isEnabled)
{
$this->isEnabled = $isEnabled;
return $this;
}
public function eraseCredentials()
{
$this->password = null;
}
public function hasRole($role)
{
return in_array($role, $this->roles);
}
public function getRoles()
{
return $this->roles;
}
public function addRole($role)
{
if (!$this->hasRole($role)) {
$this->roles[] = $role;
}
return $this;
}
public function removeRole($role)
{
$index = array_search($role, $this->roles);
if ($index !== false) {
unset($this->roles[$index]);
$this->roles = array_values($this->roles);
}
return $this;
}
public function getSalt()
{
return null;
}
}
imports:
parameters:
app.provider.member.class: AppBundle\Security\Provider\MemberProvider
app.authenticator.form_login.class: AppBundle\Security\Authenticator\FormLoginAuthenticator
services:
app.provider.member:
class: %app.provider.member.class%
arguments: ['@gateway']
app.authenticator.form_login:
class: %app.authenticator.form_login.class%
arguments: ["@service_container"]
推荐答案
经过8个小时的努力,我发现了自己的错误.我保证,在此评论之后,我会喝大量的啤酒!
I found my bug, after 8 hours of hard work. I promise, I'll drink a bulk of beers after this comment!
我在Symfony\Component\Security\Core\Authentication\Token\AbstractToken::hasUserChanged()
方法中找到了问题,该方法将会话中存储的用户与提供商的refreshUser
返回的用户进行比较.
I located my issue in the Symfony\Component\Security\Core\Authentication\Token\AbstractToken::hasUserChanged()
method, which compares user stored in the session, and the one returned by the refreshUser
of your provider.
由于以下情况,我的用户实体被视为已更改:
My user entity was considered changed because of this condition:
if ($this->user->getPassword() !== $user->getPassword()) {
return true;
}
实际上,在存储在会话中之前,在您的用户实体上调用了eraseCredentials()
方法,因此删除了密码.但是提供者返回的用户中存在密码.
In fact, before being stored in the session, the eraseCredentials()
method is called on your user entity so the password is removed. But the password exists in the user the provider returns.
这就是为什么在文档中它们显示plainPassword
和password
属性...在会话中保留password
的原因,而deleteCredentials只是清除`plainPassword.有点棘手.
That's why in documentations, they show plainPassword
and password
properties... They keep password
in the session, and eraseCredentials just cleans up `plainPassword. Kind of tricky.
Se我们有2个解决方案:
Se we have 2 solutions:
-
没有
eraseCredentials
不输入密码,如果您想以某种方式更改其成员的身份来取消身份验证,则很有用.
having
eraseCredentials
not touching password, can be useful if you want to unauthent your member when he changes his password somehow.
在我们的用户实体中实现EquatableInterface
,因为在上述测试之前调用了以下测试.
implementing EquatableInterface
in our user entity, because the following test is called before the one above.
if ($this->user instanceof EquatableInterface) {
return !(bool) $this->user->isEqualTo($user);
}
我决定在用户实体中实现EquatableInterface
,以后我将永远不会忘记这样做.
I decided to implement EquatableInterface
in my user entity, and I'll never forget to do it in the future.
<?php
namespace AppBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
class Member implements UserInterface, EquatableInterface
{
// (...)
public function isEqualTo(UserInterface $user)
{
return $user->getId() === $this->getId();
}
}
这篇关于Symfony&警卫:“由于AccountStatusException,安全令牌已删除".的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!