Symfony2 авторизация без Doctrine2 для чайника

Казалось бы, что может быть проще авторизации, но у Symfony2 на это свое мнение и я хочу поделиться с Вами, как это сделать правильно. 

Вариант авторизации, который я предлагаю, работает начиная с версии 2.4 и могу Вас заверить, это самый быстрый и простой для понимания, из всех остальных предлагаемых правильных вариантов авторизации.

1. попробуйте сначала выполнить все указанные мной действия на только-что установленной Symfony, потом уже будете пробовать на своем уже работающем проекте, иначе можете запутаться.

2. изначально, в Symfony нахождение пользователя в базе данных происходит по полю username, которое в Вашей базе данных должно быть уникальным (Вы это увидите ниже), а для нахождения пользователя Symfony использует класс, который называет провайдер, я этот класс назвал UserProvider, об этом позже.

И так, поехали, настроим app/config/security.yml

security:
    encoders:
        Acme\DemoBundle\Entity\User:
            algorithm:        sha1
            encode_as_base64: false
            iterations:       1

    providers:
        webservice:
            id: userProvider

    firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        demo_secured_area:
            anonymous: true
            pattern:    ^/
            simple_form:
                authenticator: userAuthenticator
                check_path: _demo_security_check
                login_path: _demo_login
            logout:
                path:   _demo_logout
                target: _demo

    access_control:
        - { path: ^/, role: IS_AUTHENTICATED_ANONYMOUSLY }

Объявим 2 сервиса в файле app/config/services.yml или app/config/config.yml

services:
    userProvider:
        class: Acme\DemoBundle\Security\UserProvider

    userAuthenticator:
        class:     Acme\DemoBundle\Security\UserAuthenticator
        arguments: ["@security.password_encoder"]

Теперь создадим эти 2 сервиса:

Сначала создадим класс проверки пароля src/Acme/DemoBundle/Security/UserAuthenticator.php

<?php
/**
 * По мотивам http://symfony.com/doc/current/cookbook/security/custom_password_authenticator.html
 * Класс авторизации (не аутентификации) - возвращает менеджеру ( Symfony AuthenticationManager ), а тот, в свою
 * очередь listener’y авторизованный token или вызывает Exception с ошибкой. Для проверки пользователя использует
 * userProvider, который и проверяет существует ли пользователь.
 */
namespace Acme\DemoBundle\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class UserAuthenticator implements SimpleFormAuthenticatorInterface
{
    private $encoder;

    public function __construct(UserPasswordEncoderInterface $encoder)
    {
        $this->encoder = $encoder;
    }

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        try {
            $user = $userProvider->loadUserByUsername($token->getUsername());
        } catch (UsernameNotFoundException $e) {
            throw new AuthenticationException('Invalid username or password');
        }

        $passwordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());

        if ($passwordValid) {
            return new UsernamePasswordToken(
                  $user,
                  $user->getPassword(),
                  $providerKey,
                  $user->getRoles()
            );
        }

        throw new AuthenticationException('Invalid password');
    }

    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof UsernamePasswordToken && $token->getProviderKey() === $providerKey;
    }

    public function createToken(Request $request, $username, $password, $providerKey)
    {
        return new UsernamePasswordToken($username, $password, $providerKey);
    }
}

выше описанный класс, проверяет правильность пароля с помощью метода isPasswordValid() и закономерный вопрос - как он может проверить правильность пароля, ведь он не знает данные пользователя? Вы правы, и для этого он пользуется провайдером - классом, который умеет вытягивать данные из базы данных. Так давайте создадим этот класс - провайдер src/Acme/DemoBundle/Security/UserProvider.php, который будет находить пользователя и его пароль в базе данных, по полю username

<?php
/**
 * По мотивам: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
 * Провайдер пользователя - находит пользователя по уникальному полю username.
 */
namespace Acme\DemoBundle\Security;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Acme\DemoBundle\Entity\User;

class UserProvider implements UserProviderInterface
{
    /**
     * находит и возвращает экземпляр класса User или выбрасывает Exception.
     *
     * @param string $username - мыло пользователя
     *
     * @return false|User|\Symfony\Component\Security\Core\User\UserInterface
     * @throws \Symfony\Component\Security\Core\Exception\UsernameNotFoundException
     */
    public function loadUserByUsername( $username = '' )
    {
        if (empty($username)) {
            throw new UsernameNotFoundException('Username is empty.');
        }

        $r = mysq_fetch_assoc(mysql_query("SELECT * FROM User WHERE username = '".$username."'"));

        if(empty($r)){
            throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
        }

        $user = new User();
        $user->setUsername($r['username']);
        $user->setPassword($r['password']);// напомню, это хэш пароля (как его создать - читай ниже)
        return $user;
    }

    /**
     * метод проверяет вид сущности пользователя (ведь их может быть много)
     *
     * @param UserInterface $user
     *
     * @return User|UserInterface
     * @throws \Symfony\Component\Security\Core\Exception\UnsupportedUserException
     */
    public function refreshUser( UserInterface $user )
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException( sprintf( 'Instances of "%s" are not supported.', get_class( $user ) ) );
        }
        return $this->loadUserByUsername( $user->getUsername() );
    }

    /**
     * Метод проверки класса пользователя
     * нужен чтобы Symfony использовал правильный класс Пользователя для получения объекта пользователя
     *
     * @param string $class
     *
     * @return bool
     */
    public function supportsClass( $class )
    {
        return $class === 'Acme\\DemoBundle\\Entity\\User';
    }
}

Имейте ввиду - пароль в базе данных должен храниться в виде хэша, ведь выше, в файле  мы описали алгоритм хэширования пароля. Как создавать хэш пароля, напишу в самом низу статьи.

И наконец мы обязаны создать класс, описывающий поля пользователя src/Acme/DemoBundle/Entity/User.php

<?php

namespace Acme\DemoBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;

class User implements UserInterface, EquatableInterface
{
    private $id;
    private $username;
    private $password;
    private $salt;

    public function setId( $v )
    {
        $this->id = $v;
    }

    public function setUsername( $v )
    {
        $this->username = $v;
    }

    public function setPassword( $v )
    {
        $this->password = $v;
    }

    /**
     * @inheritDoc
     * создан, т.к. этого требует интерфейс UserInterface
     * @return string - обязан возвращать уникальный параметр пользователя (например уникальное имя или емэйл)
     */
    public function getUsername()
    {
        return (string)$this->username;
    }

    /**
     * @inheritDoc
     * создан, т.к. этого требует интерфейс UserInterface
     * отдает соль для хэширования пароля пользователя
     * @return null|string
     */
    public function getSalt()
    {
        // you *may* need a real salt depending on your encoder
        // see section on salt below
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * @inheritDoc
     */
    public function getRoles()
    {
        return empty($this->roles)? array('ROLE_USER') : explode(',', $this->roles);
    }

    /**
     * @inheritDoc
     * создан, т.к. этого требует интерфейс UserInterface
     * идея в следующем: удаляет конфиденциальные данные о пользователе. Это важно, т.к.,
     * конфиденциальная информация (например, plain-text password хранится в этом объекте).
     */
    public function eraseCredentials()
    {
    }

    /**
     * проверка объекта пользователя полученного из токена с объектом полученным из провайдера
     * запускается каждый раз, при рефреше страницы (но только если в app/config/security.yml выставлен access_control)
     * @param UserInterface $user
     * @return bool
     */
    public function isEqualTo(UserInterface $user)
    {
        if (!$user instanceof User) {
            return false;
        }

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

        if ($this->salt !== $user->getSalt()) {
            return false;
        }

        if ($this->username !== $user->getUsername()) {
            return false;
        }

        return true;
    }
}

Собственно это все, теперь самое время почистить кэш: rm -rf app/cache/* и попробовать авторизоваться на странице http://symfony.local/app_dev.php/demo/secured/login

Как создавать хэш пароля

В каком-нибудь из своих контроллеров воспользуйтесь ниже описанным кодом. Итак, первый вариант - простой:

$user = new User();
$hash = $this->get('security.password_encoder')->encodePassword($user, 'пароль пользователя');

Второй вариант - сложнее (для заметки):

$user = new User();
$hash = $this->get('security.encoder_factory')->getEncoder($user)->encodePassword('пароль пользователя', $user->getSalt());

Вот такие пироги товарищи, надеюсь разжевал хорошо, но если будут вопросы - подумайте и затем пишите, удачки всем.

Оцени публикацию:
  • 8,35
Оценили человек: 8
Теги : symfony

Похожие статьи:

Справочники и учебники:


Предложения и пожелания:
Ваше имя:
Ваш E-mail:
Сколько будет Οдин + Τри
Главная
X

youtube.com/watch?v=7hFivbgIEqk

При полном или частичном использовании материалов данного сайта, ссылка на сайт "yapro.ru" обязательна как на источник информации.
Автоматический импорт материалов и информации с сайта запрещен.
Copyrights © 2007 - 2017 YaPro.Ru

Главная » Веб-мастеру » PHP »