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

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

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

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

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


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

Новые заметки:

Про что мы забываем когда делаем оценку задачи по времени

Список вопросов для собеседования разработчика по телефону

Phpstorm7 LiveEdit

Жесткий хабр или не хабр, тогда кто?

Яндекс.Деньги мошенничество

Как узнать какие страницы в поиске яндекса или это секрет

Защита сервера от ошибок в phpMyAdmin

Последние комменты:

Yapro CMS:

Здравствуйте, Гость | Войти | Регистрация | Карта сайта | RSS ленты | Ошибка в тексте? Выделите её мышкой и нажмите: Ctrl + Enter

youtube.com/watch?v=7hFivbgIEqk

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

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