[Symfony 2] Security Bundle – Benutzer mit username oder email anmelden.

Augenscheinlich unterstützt das Security-Module nur die Authentifizierung via Benutzername und Password. Wie man sich mit einem Benutzernamen ODER der E-Mail-Adresse und einem Password authentifiziert, ist ein wenig versteckt. So gehts:

Schaut man sich den generischen EntityUserProvider an, so sieht man ab Zeile 46 sowas wie

    /**
     * {@inheritdoc}
     */
    public function loadUserByUsername($username)
    {
        if (null !== $this->property) {
            $user = $this->repository->findOneBy(array($this->property => $username));
        } else {
            if (!$this->repository instanceof UserProviderInterface) {
                throw new InvalidArgumentException(sprintf('The Doctrine repository "%s" must implement UserProviderInterface.', get_class($this->repository)));
            }

            $user = $this->repository->loadUserByUsername($username);
        }

        if (null === $user) {
            throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
        }

        return $user;
    }

Dieser Code besagt, dass nur dann, wenn die Konfigurationeinstellung “property” aus der security.yml entfernt wird (steht üblicherweise auf “username” und gibt das Datenbankattribut an, das eben den Benutzernamen hält), das entsprechende Doctrine-Entity “User” auch eine Repository-Class hat und diese darüber hinaus UserProviderInterface implementiert, man seine eigene loadUserByUsername()-Methode implementieren kann, die dann von der Firewall zur Identifizierung des Benutzer beim Login herangezogen wird (dieser Vorgang ist komplett intransparent, Symfony Magic).

Also aus seiner “alten” security.yml

    providers:
        default:
            users:
                user:  { password: userpass, roles: [ 'ROLE_USER' ] }
                admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
            entity: { class: DvlpCoreBundleEntityUser, property: username }

sowas machen:

     ...
            entity: { class: DvlpCoreBundleEntityUser }

Dann muss das zu User gehörtige UserRepository nur noch UserProviderInterface implementieren, bspw. so:

<?php

namespace DvlpCoreBundleEntity;

use DoctrineORMEntityRepository;
use SymfonyComponentSecurityCoreUserUserProviderInterface;
use SymfonyComponentSecurityCoreUserUserInterface;

/**
 * UserRepository
 *
 * This class was generated by the Doctrine ORM. Add your own custom
 * repository methods below.
 */
class UserRepository extends EntityRepository implements UserProviderInterface
{
    /**
     * {@inheritdoc}
     */
    public function loadUserByUsername($username)
    {
        return $this->getEntityManager()
            ->createQuery('SELECT u FROM DvlpCoreBundle:User u JOIN u.Profile p WHERE u.username = :username OR p.email = :username')
            ->setParameters(array(
                'username' => $username
            ))
            ->getOneOrNullResult();
    }

    /**
     * {@inheritDoc}
     */
    public function refreshUser(UserInterface $user)
    {
        return $this->loadUserByUsername($user->getUsername());
    }

    /**
     * {@inheritDoc}
     */
    public function supportsClass($class)
    {
        // NEVER CALLED ...
        return $class === 'DvlpCoreBundleEntityUser';
    }
}
}

Schon kann man als Benutzernamen entweder den Username oder die E-Mail-Adresse des Users angeben.