Вход в Symfony2 AJAX - программирование
Подтвердить что ты не робот

Вход в 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.

4b9b3361

Ответ 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. См. этот вопрос