В первой части мы обсудили основы настройки системы безопасности в нашем приложении (настройки базы данных и security.yml
Мы также рассмотрели этап предварительной регистрации, когда пользователь проверяет статус своего приглашения в приложении.
В этой статье мы поговорим о регистрации, входе в систему и действиях после входа в систему.
Форма, база данных и многое другое
Регистрация осуществляется через форму. Пользователь вводит информацию, такую как электронная почта, имя пользователя, пароль, подтвержденный пароль, и в некоторых случаях принимает отказ от ответственности.
Мы также знаем, что пользовательский объект в конечном итоге будет сохранен в user
Во время этого процесса настойчивости мы должны знать, что:
- Некоторая форма ввода будет использоваться при заполнении объекта пользователя (например,
username
password
- Некоторые пользовательские свойства будут установлены приложением (например,
created
- Некоторые формы ввода предназначены только для проверки и отбрасываются (например, повторно введенный пароль, проверка на отказ от ответственности).
У нас должен быть способ создать «связь» между формой и базовой таблицей и указать вышеуказанные требования.
В Symfony мы достигаем этого, объявляя специальный класс типа формы, связанный с сущностью. В этом случае RegistrationType
Этот класс ( RegistrationType
src/AppBundle/Form/Type/RegistrationType.php
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('username', 'text', ['label'=>'User Name'])
->add('password', 'password',['label'=>'Password'])
->add('confirm', 'password', ['mapped' => false,'label'=>'Re-type password'])
->add('homepage', 'text',['label'=>'Homepage'])
->add('email', 'hidden', ['label'=>'email'])
->add('save', 'submit', ['label'=>'Register'])
;
}
public function getName()
{
return 'registration';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\User',
]);
}
}
У нас есть несколько вызовов ->add()
или поле формы, которое нужно отобразить, но не отобразить в поле базовой таблицы. Любое поле таблицы, не добавленное, не будет отображаться и, следовательно, не будет заполнено пользователем.
Давайте посмотрим на некоторые примеры:
add('username', 'text', ['label'=>'User Name'])
Это добавляет поле формы текстового типа, сопоставляемое с полем username
add('confirm', 'password', ['mapped' => false,'label'=>'Re-type password'])
Это добавляет поле формы с типом пароля, но не отображается в поле таблицы и имеет метку «Повторить ввод пароля».
add('email', 'hidden', ['label'=>'email'])
Это добавляет скрытое поле формы, сопоставляя его с полем таблицы email
Установка ярлыка здесь бесполезна, но нет вреда в том, что она там есть.
После того как RegistrationType
$registration = new User();
$form = $this->createForm(new RegistrationType(), $registration, ['action' => $this->generateUrl('create'), 'method' => 'POST']);
return $this->render('AppBundle:Default:register2.html.twig', ['form' => $form->createView(), 'email' => $email]);
Мы создали новый пользовательский экземпляр, а затем использовали createForm
RegistrationType
$registration
Наконец, мы отображаем регистрационную форму.
Визуализация формы в шаблоне представления
Предоставленная форма регистрации выглядит следующим образом:
Код для визуализации формы выглядит следующим образом:
<form class="form-signin" name='register_form' id='register_form' method='post' action='{{path('create')}}'>
{{ form_widget(form.username, {'attr': {'class': 'form-control', 'placeholder':'User Name'}}) }}<br>
{{ form_widget(form.password, {'attr': {'class': 'form-control', 'placeholder':'Password'}}) }}
{{ form_widget(form.confirm, {'attr': {'class': 'form-control', 'placeholder':'Confirm Password'}}) }}
{{ form_widget(form.homepage, {'attr': {'class': 'form-control', 'placeholder':'个人主页'}}) }}
{{ form_widget(form.email, {'attr': {'value': email}}) }}
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me" required checked>Disclaimer
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Register</button>
</form>
Я должен признаться, что отображать форму, созданную вышеописанным процессом, не легкая работа. К счастью, Twig предоставляет нам несколько вспомогательных функций для настройки этой визуализированной формы.
{{ form_widget(form.password, {'attr': {'class': 'form-control', 'placeholder':'Password'}}) }}
Используя помощник form_widget
password
Более важной частью является второй параметр, который дополнительно определяет визуализированный элемент HTML5. В приведенном выше коде мы указали, что элемент <input>
password
form-control
Обратите внимание, что мы не указали тип этого поля формы — это должно быть поле пароля, поскольку мы должны вводить пароль. form_widget
$form = $this->createForm(...)
RegistrationType
Создание пользователя
Когда пользователь нажимает кнопку «Зарегистрироваться», информация будет обрабатываться дальше, и если все пройдет хорошо, пользователь будет создан.
public function createAction(Request $req)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new RegistrationType(), new User());
$form->handleRequest($req);
$user= new User();
$user= $form->getData();
$user->setCreated(new \DateTime());
$user->setRoles('ROLE_USER');
$user->setGravatar('http://www.gravatar.com/avatar/'.md5(trim($req->get('email'))));
$user->setActive(true);
$pwd=$user->getPassword();
$encoder=$this->container->get('security.password_encoder');
$pwd=$encoder->encodePassword($user, $pwd);
$user->setPassword($pwd);
$em->persist($user);
$em->flush();
$url = $this->generateUrl('login');
return $this->redirect($url);
}
}
В этом сегменте кода мы сделаем много вещей, связанных с фактическим созданием пользователя из формы ввода.
-
$this->createForm
RegistrationType
- Объект формы будет обрабатывать пользовательский ввод.
- Мы создаем пустой объект
User
$form->getData()
- Мы начинаем присваивать не заполненные пользователем свойства: дату создания, роль, граватар и т. Д.
- Пользователь может ввести свой пароль только в виде простого текста, и приложение берет на себя ответственность за его хеширование. Это именно то, что делают эти две строки кода.
$encoder = $this->container->get('security.password_encoder');
$pwd = $encoder->encodePassword($user, $pwd);
Обратите внимание, что в этих двух строках мы даже не сообщаем коду, какой метод кодирования мы на самом деле используем. Symfony просто ищет encoder
ПРИМЕЧАНИЕ. Ваша устаревшая установка PHP может не включать bcrypt
Если это так, пожалуйста, используйте composer для установки ircmaxell/password-compat
ПРИМЕЧАНИЕ. Обработка ввода формы Symfony 2 и манипулирование базой данных безопасны с точки зрения того, что она обрабатывает все необходимые экранирующие функции для предотвращения злонамеренного ввода и SQL-инъекций. Таким образом, мы можем назначить входные данные для соответствующих полей.
Войти и опубликовать логин
Когда мы осуществляем управление пользователями, как указано выше, процесс входа в систему прост. Мы уже определили два маршрута, связанных с входом в систему:
login:
path: /login
defaults: { _controller: AppBundle:Security:login}
login_check:
path: /login_check
Далее мы создадим шаблон для отображения основной формы входа в систему:
<form class="form-signin" method='post' action='{{path('login_check')}}'>
{% if error %}
<div class='red'>{{ error.message }}</div><br>
{% endif %}
<label for="inputName" class="sr-only">User Name</label>
<input type="text" id="inputName" name='_username' class="form-control" placeholder="User Name" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" name="_password" class="form-control" placeholder="Password" required>
<div class="checkbox">
<label>
<input type="checkbox" value="remember-me" required checked>Disclaimer
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
</form>
Есть только две вещи, на которые стоит обратить внимание:
- Действие этой формы входа должно указывать на
{{path('login_check'}}
/login_check
- В нашем примере мы используем имя пользователя + пароль в качестве учетных данных. Таким образом, два входа в форме ДОЛЖНЫ быть названы «
_username
_password
Это требуется системой безопасности Symfony.
Мы также можем заметить, что, поскольку мы не используем «тип формы» для связи информации для входа в систему с базовым пользовательским объектом (как мы делаем для регистрации), и вместо этого мы оставляем это для интерфейса безопасности, мы создали виджеты формы все сами.
Вот и все. Теперь пользователь может ввести имя пользователя и пароль и войти в систему.
После успешного входа нам нужно знать о нескольких вещах:
- В контроллере мы можем использовать
$this->getUser()
user
User
- В Twig мы можем использовать определенные вспомогательные функции для доступа к объекту пользователя.
Как мы видим в части 1, is_granted('ROLE_ADMIN')
Интересно отметить, что интерфейс управления безопасностью Symfony не предоставляет интуитивно понятного способа, позволяющего приложению выполнять некоторые действия после входа в систему. НЕТ такой вещи как:
after_login:
path: /login_after
В нашем приложении нам действительно нужно что-то делать после входа пользователя в систему. Нам нужно обновить дату и время последнего входа пользователя в систему (поле для logged
Чтобы выполнить эту простую задачу, нам нужно немного настроить наше приложение.
Сначала мы регистрируем сервис (для «после успешного входа в систему») в service.yml
services:
security.authentication.success_handler:
class: AppBundle\Handler\AuthenticationSuccessHandler
arguments: [@security.http_utils, @service_container, {}]
tags:
- { name: 'monolog.logger', channel: 'security'}
Затем мы создаем файл src/AppBundle/Handler/AuthenticationHandler.php
class AuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler
{
protected $container;
public function __construct(HttpUtils $httpUtils, \Symfony\Component\DependencyInjection\ContainerInterface $cont, array $options)
{
parent::__construct($httpUtils, $options);
$this->container=$cont;
}
public function onAuthenticationSuccess(\Symfony\Component\HttpFoundation\Request $request, \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token)
{
$user=$token->getUser();
$user->setLogged(new \DateTime());
$em=$this->container->get('doctrine.orm.entity_manager');
$em->persist($user);
$em->flush();
return $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
}
}
Чтобы сделать хороший обработчик «post-login» в нашем случае, есть три вещи первостепенной важности.
- Мы должны получить доступ к объекту пользователя, чтобы мы могли обновить время последнего входа пользователя.
- Мы должны получить доступ к диспетчеру сущностей, чтобы время входа в систему можно было сохранить в таблице.
- Мы должны получить доступ к HTTP-запросу, чтобы после обновления времени последнего входа приложение все равно могло перенаправить нас на «целевой» URI.
Все это выполняется с помощью аргументов, передаваемых onAuthenticationSuccess
arguments: [@security.http_utils, @service_container, {}]
onAuthenticationSuccess
- Сам пользовательский объект доступен в методе
$token->getUser()
@service_container
- Менеджер сущностей базы данных доступен через контейнер службы, переданный в (
$em = $this->container->get('doctrine.orm.entity_manager');
$this->httpUtils->createRedirectResponse
, - Перенаправление выполняется с помощью
@security.http_utils
determineTargetUrl
Обратите внимание, что метод defineTargetUrl вызывается для создания URI перенаправления на основе $request
Обычно мы можем посещать различные URI: страницу индекса или конкретную ссылку на сообщение. Мы можем взглянуть на реализацию этого метода в исходном коде Symfony 2 ( project_root/vendor/symfony/symfony/src/Symfony/Component/Security/Http/Authentication/AuthenticationSuccessHandler.php
protected function determineTargetUrl(Request $request)
{
if ($this->options['always_use_default_target_path']) {
return $this->options['default_target_path'];
}
if ($targetUrl = $request->get($this->options['target_path_parameter'], null, true)) {
return $targetUrl;
}
if (null !== $this->providerKey && $targetUrl = $request->getSession()->get('_security.'.$this->providerKey.'.target_path')) {
$request->getSession()->remove('_security.'.$this->providerKey.'.target_path');
return $targetUrl;
}
if ($this->options['use_referer'] && ($targetUrl = $request->headers->get('Referer')) && $targetUrl !== $this->httpUtils->generateUri($request, $this->options['login_path'])) {
return $targetUrl;
}
return $this->options['default_target_path'];
}
}
Он объясняет логику того, как в конечном итоге определяется целевой URI (обычно URI, который инициирует вход в систему).
Вывод
Мы только что успешно рассмотрели два важных аспекта разработки приложений с помощью Symfony2:
- Регистрация (и приглашение)
- Войти (и опубликовать логин)
Недавняя тенденция на веб-сайтах — использование учетных данных социальных сетей (G +, Facebook и т. Д.) Для упрощения процесса регистрации / входа в систему. Тем не менее, чистая внутренняя регистрация / вход в систему по-прежнему имеет решающее значение для некоторых приложений. Более того, понимание всего процесса регистрации / входа в систему помогает нам понять систему безопасности Symfony2.
Если вы хотите увидеть больше информации по смежным вопросам, например, о валидации, или просто оставить комментарии или отзывы об этом руководстве, сообщите нам об этом!