Вход в Symfony2 AJAX
У меня есть пример, где я пытаюсь создать логин AJAX, используя Symfony2 и FOSUserBundle. Я устанавливаю свои собственные success_handler
и failure_handler
под form_login
в моем файле security.yml
.
Вот класс:
class AjaxAuthenticationListener implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
/**
* This is called when an interactive authentication attempt succeeds. This
* is called by authentication listeners inheriting from
* AbstractAuthenticationListener.
*
* @see \Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener
* @param Request $request
* @param TokenInterface $token
* @return Response the response to return
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => true);
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
/**
* This is called when an interactive authentication attempt fails. This is
* called by authentication listeners inheriting from
* AbstractAuthenticationListener.
*
* @param Request $request
* @param AuthenticationException $exception
* @return Response the response to return
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => false, 'message' => $exception->getMessage());
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
}
Это отлично подходит для обработки как успешных, так и неудачных попыток входа в AJAX. Однако, когда включено - я не могу войти в систему через стандартный метод POST (не-AJAX). Я получаю следующую ошибку:
Catchable Fatal Error: Argument 1 passed to Symfony\Component\HttpKernel\Event\GetResponseEvent::setResponse() must be an instance of Symfony\Component\HttpFoundation\Response, null given
Я бы хотел, чтобы мои переопределения onAuthenticationSuccess
и onAuthenticationFailure
выполнялись только для XmlHttpRequests (запросы AJAX) и просто передавали выполнение обратно исходному обработчику, если нет.
Есть ли способ сделать это?
TL; DR Я хочу, чтобы AJAX запросил попытки входа в систему вернуть ответ JSON для успеха и неудачи, но я хочу, чтобы он не влиял на стандартный вход через форму POST.
Ответы
Ответ 1
Давид answer хорош, но ему не хватает деталей для newbs - так это заполнение пробелов.
В дополнение к созданию AuthenticationHandler вам необходимо настроить его как службу, используя конфигурацию службы в комплекте, в котором вы создали обработчик. Генерация пакетов по умолчанию создает файл xml, но я предпочитаю yml. Вот пример файла services.yml:
#src/Vendor/BundleName/Resources/config/services.yml
parameters:
vendor_security.authentication_handler: Vendor\BundleName\Handler\AuthenticationHandler
services:
authentication_handler:
class: %vendor_security.authentication_handler%
arguments: [@router]
tags:
- { name: 'monolog.logger', channel: 'security' }
Вам нужно будет изменить расширение пакета DependencyInjection, чтобы использовать yml вместо xml так:
#src/Vendor/BundleName/DependencyInjection/BundleExtension.php
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
Затем в конфигурации безопасности приложения вы установили ссылки на только что определенную службу authentication_handler:
# app/config/security.yml
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
success_handler: authentication_handler
failure_handler: authentication_handler
Ответ 2
namespace YourVendor\UserBundle\Handler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class AuthenticationHandler
implements AuthenticationSuccessHandlerInterface,
AuthenticationFailureHandlerInterface
{
private $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
// Handle XHR here
} else {
// If the user tried to access a protected resource and was forces to login
// redirect him back to that resource
if ($targetPath = $request->getSession()->get('_security.target_path')) {
$url = $targetPath;
} else {
// Otherwise, redirect him to wherever you want
$url = $this->router->generate('user_view', array(
'nickname' => $token->getUser()->getNickname()
));
}
return new RedirectResponse($url);
}
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) {
// Handle XHR here
} else {
// Create a flash message with the authentication error message
$request->getSession()->setFlash('error', $exception->getMessage());
$url = $this->router->generate('user_login');
return new RedirectResponse($url);
}
}
}
Ответ 3
Если вам нужна поддержка ошибок формы FOS UserBundle, вы должны использовать:
$request->getSession()->set(SecurityContext::AUTHENTICATION_ERROR, $exception);
вместо:
$request->getSession()->setFlash('error', $exception->getMessage());
В первом ответе.
(конечно, помните о заголовке: используйте Symfony\Component\Security\Core\SecurityContext;)
Ответ 4
Я обработал это полностью с помощью javascript:
if($('a.login').length > 0) { // if login button shows up (only if logged out)
var formDialog = new MyAppLib.AjaxFormDialog({ // create a new ajax dialog, which loads the loginpage
title: 'Login',
url: $('a.login').attr('href'),
formId: '#login-form',
successCallback: function(nullvalue, dialog) { // when the ajax request is finished, look for a login error. if no error shows up -> reload the current page
if(dialog.find('.error').length == 0) {
$('.ui-dialog-content').slideUp();
window.location.reload();
}
}
});
$('a.login').click(function(){
formDialog.show();
return false;
});
}
Вот класс AjaxFormDialog. К сожалению, я до сих пор не портировал его на плагин jQuery... https://gist.github.com/1601803
Ответ 5
Вы должны вернуть объект Response в обоих случаях (Ajax или нет). Добавьте "else", и вам хорошо идти.
Реализация по умолчанию:
$response = $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
in AbstractAuthenticationListener::onSuccess
Ответ 6
Я сделал небольшой пакет для новых пользователей, чтобы предоставить форму входа AJAX: https://github.com/Divi/AjaxLoginBundle
Вам просто нужно заменить на form_login аутентификацию ajax_form_login в security.yml.
Не стесняйтесь предлагать новую функцию в отслеживании проблем Github!
Ответ 7
Возможно, это не то, что задал ОП, но я столкнулся с этим вопросом и подумал, что у других может быть такая же проблема, что и я.
Для тех, кто внедряет вход AJAX, используя метод, описанный в принятом ответе, и кто ТАКЖЕ использует AngularJS для выполнения запроса AJAX, это не будет работать по умолчанию. Angular $http
не устанавливает заголовки, которые Symfony использует при вызове метода $request->isXmlHttpRequest()
. Чтобы использовать этот метод, вам нужно установить соответствующий заголовок в запросе Angular. Это то, что я сделал, чтобы решить проблему:
$http({
method : 'POST',
url : {{ path('login_check') }},
data : data,
headers: {'X-Requested-With': 'XMLHttpRequest'}
})
Прежде чем использовать этот метод, имейте в виду, что этот заголовок не работает с CORS. См. этот вопрос