2016-10-05 13 views
6

Próbowałem utworzyć wystawcę uwierzytelnienia dla mojego formularza logowania, ale zawsze jestem niezalogowany z jakiegoś niejasnego powodu.Symfony i strażnik: "Token bezpieczeństwa został usunięty z powodu wyjątku AccountStatusException"

[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)"} [] 

Nie rozumiem tego „AuthenticationExpiredException” jak nie mam nic bezpaństwowcem, ani w żaden sposób wygasania nigdzie w mojej aplikacji.

Czy ten problem jest dostępny dla każdego?


Edycja 1

Po pęczek godzin, wygląda na to jestem BezZapisu powodu {{ is_granted('ROLE_USER') }} w patyk. I tak nie rozumiem.

Edycja 2

Gdybym dump() mój znak bezpieczeństwa na metodzie onAuthenticationSuccess Authenticator, authenticated = true.

Ale, jeśli zrzutu() mój token bezpieczeństwa po przekierowaniu lub podczas dostępu do nowej strony, 'authenticated' = false.

Dlaczego do cholery moje uwierzytelnienie nie jest przechowywane.


APP/konfiguracji/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/Kontroler/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/Forma/Typ/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/Bezpieczeństwo/Authenticator/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/Bezpieczeństwo/Dostawcy/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/Bezpieczeństwo/Użytkownik/Użytkownik. 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/Resources/config/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"] 

Odpowiedz

14

znalazłem mój błąd, po 8 godzinach ciężkiej pracy. Obiecuję, wypiję większość piw po tym komentarzu!

Znalazłem mój problem w metodzie Symfony\Component\Security\Core\Authentication\Token\AbstractToken::hasUserChanged(), która porównuje użytkownika przechowywanego w sesji, i ten zwrócony przez refreshUser Twojego dostawcy.

Moja jednostka użytkownik uznano zmienił z powodu tej choroby:

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

W rzeczywistości, zanim zostaną zapisane w sesji, metoda eraseCredentials() nazywa na swojej jednostce użytkownika więc hasło zostanie usunięte. Ale hasło istnieje u użytkownika, który zwraca operator.

Dlatego w dokumentacjach pokazują one właściwości plainPassword i password ... Zachowują password w sesji, a eraseCredentials po prostu czyści `plainPassword. Trudne.

Se mamy 2 rozwiązania:

  • mający eraseCredentials nie dotykając hasło, może być przydatne, jeśli chcesz unauthent swój członek, gdy zmieni swoje hasło jakoś.

  • implementacja EquatableInterface w naszym podmiocie użytkownika, ponieważ następujący test jest wywoływany przed powyższym.

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

postanowiłem realizować EquatableInterface w mojej jednostce użytkownika, a ja nigdy nie zapomnę tego robić w przyszłości.

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