Механизм аутентификации на основе Doctrine в проекте Symfony2
Я работаю над небольшим проектом, поддерживающим Doctrine2, с использованием Symfony2 в первый раз. В настоящее время я борюсь с компонентом безопасности symfony2, точнее с механизмом аутентификации, описанным в документации .
Я хочу использовать аутентификацию на основе форм и сделал все, что указано в документах:
У меня есть файл конфигурации security.yml, который выглядит следующим образом:
security.config:
firewalls:
admin:
pattern: /admin/.*
form-login: true
logout: true
login_path: /login
check_path: /validateLogin
always_use_default_target_path: false
target_path_parameter: target
check_page:
pattern: /validateLogin
form-login: true
login_path: /login
check_path: /validateLogin
always_use_default_target_path: false
target_path_parameter: target
public:
pattern: /.*
security: false
providers:
admin:
password_encoder: md5
entity:
class: AdminBundle:User
property: username
access_control:
- { path: /admin/.*, role: ROLE_ADMIN }
- { path: /validateLogin, role: IS_AUTHENTICATED_ANONYMOUSLY }
role_hierarchy:
ROLE_ADMIN: ROLE_USER
Страница check_page исключается из области "без безопасности" после прочтения аналогичного потока на devcomments.
В моей конфигурации маршрутизации я включаю два правила аутентификации:
_security_login:
pattern: /login
defaults:
_controller: PublicBundle:Auth:index
_security_check:
pattern: /validateLogin
Класс сущности, который я использую для представления пользователя, является объектом Doctrine2 и реализует AccountInterface:
<?php
namespace Application\AdminBundle\Entity;
use Symfony\Component\Security\User\AccountInterface;
/**
* @orm:Entity
*/
class User implements AccountInterface
{
/**
* @orm:Id
* @orm:Column(type="integer")
* @orm:GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @orm:Column(type="string", length="255")
*/
protected $username;
/**
* @orm:Column(type="string", length="40")
*/
protected $password;
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
}
/**
* Implementing the AccountInterface interface
*/
public function __toString()
{
return $this->getUsername();
}
public function getRoles()
{
return array('ROLE_ADMIN');
}
public function eraseCredentials()
{
}
public function getSalt()
{
return $this->getId();
}
}
В классе AuthController я использую код примера из документов symfony2:
public function indexAction()
{
if ($this->get('request')->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $this->get('request')->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $this->get('request')->getSession()->get(SecurityContext::AUTHENTICATION_ERROR);
}
return
$this->render(
'PublicBundle:Auth:index.twig',
array(
'last_username' => $this->get('request')->getSession()->get(SecurityContext::LAST_USERNAME),
'error' => $error));
}
Теперь возникает проблема: правило перенаправления от http://symfony2.localhost/app_dev.php/admin/test до http://symfony2.localhost/app_dev.php/login работает, но после ввод имени пользователя/пароля и отправка формы для входа, я снова перенаправляюсь на URL-адрес для входа без сообщения об ошибке.
Я знаю, что это, вероятно, действительно основная проблема, но поскольку на symfony2 еще нет документации, я думаю, что это хорошее место, чтобы задавать такие вопросы, как эта.
В общем, есть некоторые моменты внутри проекта symfony2, который, кажется, работает магически (конечно, с поддержкой DI), что усложняет процесс обучения. Мои мысли о том, как работает аутентификация, заключается в том, что есть какой-то волшебный контроллер, который ловит действие validateLogin, ищет репозиторий сущности для моего объекта User, вызывает findOneBy ('username' = > $username) и сравнивает пароли... это не так ли?
Заранее благодарю за какой-либо намек, я уже несколько часов искал эту проблему...:)
Пол
Ответы
Ответ 1
Мои мысли о том, как работает аутентификация, это то, что есть волшебный контроллер, который ловит действие validateLogin, ищет репозиторий сущности для моего объекта User, вызывает findOneBy ('username' = > $username) и сравнивает пароли.. Правильно ли это?
Ты ошибаешься. Аутентификация не связана с каким-либо контроллером, поэтому вы не указываете какой-либо маршрут _security_check
. Auth основан на EventDispatcher
. Всякий раз, когда вы указываете какой-либо прослушиватель в своем брандмауэре (например, form_login
, anonymous
, logout
и т.д.), Вы фактически регистрируете новый прослушиватель для события core.security
. Symfony\Component\HttpKernel\Security\Firewall::handle()
- это место, где эти слушатели фактически зарегистрированы.
Общий, упрощенный поток:
- Пользователь заполняет регистрационную форму (поля
_username
и _password
).
- Запрос обрабатывается с помощью Symfony2.
-
core.security
событие запускается.
- EventDispatcher уведомляет всех слушателей.
-
UsernamePasswordFormAuthenticationListener
запущен (handle()
метод) и проверяет:
- URL соответствует
check_path
.
- Запрос имеет как параметры
_username
, так и _password
.
- Слушатель пытается аутентифицировать пользователя (
attemptAuthentication()
метод).
- Менеджер аутентификации запускает всех зарегистрированных поставщиков.
- Наконец, запускается
DaoAuthenticationProvider
, и он пытается получить пользователя, используя класс репозитория пользователя Doctrine.
- Если все в порядке
UsernamePasswordToken
(который содержит объект $user
, возвращаемый методом loadUserByUsername()
), возвращается и пользователь перенаправляется.
В действительности механизм безопасности довольно сложный и трудно понимаемый (документация еще не закончена). Но когда вы, наконец, поймете, как это работает, вы увидите, насколько мощный механизм.
Я написал свой собственный механизм аутентификации, и он отлично работает.
-
Конфигурация:
Я использую пользовательский поставщик и кодер.
security.config:
providers:
main:
id: project.user_repository # DI id. Doctrine UserRepositry
check_path: /login-check
encoders:
main:
class: Project\SiteBundle\Entity\User
id: security.encoder.sha512 # DI id. Service %security.encoder.digest.class% (with "sha512" as first parameter)
firewalls:
restricted:
pattern: /panel/.*
form_login:
check_path: /login-check
public:
pattern: /.*
anonymous: true
form_login:
check_path: /login-check
logout: true
access_control:
- { path: /panel/.*, role: ROLE_USER }
- { path: /.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
Как вы можете видеть, /panel/*
ограничен, а /*
является общедоступным.
-
Сервис security.encoder.sha512
- встроенный кодировщик:
<service id="security.encoder.sha512" class="%security.encoder.digest.class%">
<argument>sha512</argument>
</service>
-
Project\SiteBundle\Entity\User
:
/**
* @orm:Entity(repositoryClass="Project\SiteBundle\Repository\UserRepository")
*/
class User implements AdvancedAccountInterface {
/**
* @orm:Id @orm:Column(type="integer")
* @orm:GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @orm:Column(unique=true, nullable=true)
*/
protected $email;
/**
* @orm:Column(unique=true, nullable=true)
*/
protected $xmpp;
/**
* @orm:Column(length=128)
*/
protected $password;
/**
* @orm:Column(length=16)
*/
protected $salt;
// User can be logged in using email address or xmpp adress.
// Dozens of getters/setters here.
}
-
Project\SiteBundle\Repository\UserRepository
class UserRepository extends EntityRepository implements UserProviderInterface {
public function loadUserByUsername($username) {
$dql = sprintf('
SELECT u
FROM %s u
WHERE u.email = :id OR u.xmpp = :id
', $this->_entityName);
$user = null;
try {
$user = $this->_em->createQuery($dql)->setParameter('id', $username)->getSingleResult();
} catch (ORMException $e) {
throw new UsernameNotFoundException("User $username not found.", $e->getCode(), $e);
}
return $user;
}
public function loadUserByAccount(AccountInterface $user) {
return $this->loadUserByUsername($user->getUsername());
}
}
-
Маршруты и контроллер безопасности такие же, как ваши.
Ответ 2
Вы должны использовать https://github.com/FriendsOfSymfony/FOSUserBundle FOS UserBundle, он реализует все это с помощью Doctrine 2 и имеет множество функций.
Ответ 3
Причина, по сути, почему страница входа в систему снова загружается без сообщения об ошибке, потому что по иронии судьбы настройки безопасности не настроены, чтобы анонимный доступ к странице входа.