繁体   English   中英

Symfony&Guard:“由于AccountStatusException,安全令牌已被删除”

[英]Symfony & Guard: “The security token was removed due to an AccountStatusException”

我尝试为我的登录表单创建一个身份验证器,但由于一些不明原因,我总是没有记录。

[2016-10-05 18:54:53] security.INFO: Guard authentication successful! {"token":"[object] (Symfony\\Component\\Security\\Guard\\Token\\PostAuthenticationGuardToken: PostAuthenticationGuardToken(user=\"test@test.test\", 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”,因为我没有任何无状态,​​也没有在我的应用程序中任何方式的任何过期。

这个问题对任何人都有用吗?


编辑1

经过一段时间后,由于Twig中的{{ is_granted('ROLE_USER') }} ,我看起来好像没有记录。 反正不明白为什么。

编辑2

如果我在onAuthenticationSuccess身份验证器的方法上转储我的安全令牌,则authenticated = true

但是,如果我在重定向或访问新页面后转储()我的安全令牌, 'authenticated' = false

为什么我的身份验证没有存储。


应用程序/配置/ security.yml

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" }

的appbundle /控制器/ SecurityController.php

<?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(),
        ];
    }
}

的appbundle /表/类型/ ConnectType.php

<?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',
           ])
        ;
    }
}

的appbundle /安全/认证者/被FormLoginAuthenticator.php

<?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')
        ;
    }
}

的appbundle /安全/供应商/ MemberProvider.php

<?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;
    }
}

的appbundle /安全/用户/ Member.php

<?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;
    }
}

SRC /的appbundle /资源/配置/ services.yml

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个小时的努力,我发现了我的虫子。 我保证,在评论之后,我会喝大量的啤酒!

我将我的问题放在Symfony\\Component\\Security\\Core\\Authentication\\Token\\AbstractToken::hasUserChanged()方法中,该方法比较存储在会话中的用户和提供者的refreshUser返回的refreshUser

由于这种情况,我的用户实体被视为已更改

    if ($this->user->getPassword() !== $user->getPassword()) {
        return true;
    }

实际上,在存储在会话中之前,会在用户实体上调用eraseCredentials()方法,以便删除密码。 但密码存在于提供程序返回的用户中。

这就是为什么在文档中,它们显示了plainPasswordpassword属性......它们在会话中保留password ,eraseCredentials只清理`plainPassword。 有点棘手。

我们有2个解决方案:

  • 如果您想以某种方式更改密码时取消您的会员身份,则eraseCredentials不会触及密码会非常有用。

  • 在我们的用户实体中实现EquatableInterface ,因为在上面的测试之前调用了以下测试。

     if ($this->user instanceof EquatableInterface) { return !(bool) $this->user->isEqualTo($user); } 

我决定在我的用户实体中实现EquatableInterface ,我将永远不会忘记这样做。

<?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();
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM