2011-12-22 3 views
40

У меня есть пример, где я пытаюсь создать вход AJAX с помощью Symfony2 и FOSUserBundle. Я устанавливаю свои success_handler и failure_handler под номером form_login в своем файле security.yml.Symfony2 AJAX Login

Вот класс:

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

У меня такая же точная проблема. Я задал вопрос здесь: http://stackoverflow.com/questions/8654265/calling-default-handler-from-a-custom-authentication-handler, но на данный момент ответов нет :( –

+1

Там есть отличная статья, объясняющая, как это сделать это здесь: http://www.webtipblog.com/adding-an-ajax-login-form-to-a-symfony-project/ – joe42

ответ

48

Давид answer хорош, но ему не хватает мелочей для новичков - так это заполнить пробелы.

В дополнение к созданию AuthenticationHandler вам необходимо настроить его как услугу, используя конфигурацию службы в комплекте, в котором вы создали обработчик. Генерация пакетов по умолчанию создает файл xml, но я предпочитаю yml. Вот пример услуг.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' } 

Вы должны были бы изменить расширение внедрение зависимости расслоение использовать 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 
+0

В конфигурации services.yml есть опечатка. Перед каналом требуется запятая (,) . - {name: 'monolog.logger', channel: 'security'} –

+0

Спасибо, это помогло заполнить пробелы от Davids Answer! –

2

Вы должны вернуть объект Response в обоих случаях (Ajax или нет). Добавьте «else», и вам хорошо идти.

Реализация по умолчанию:

$response = $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request)); 

в AbstractAuthenticationListener::onSuccess

+0

Проблема в том, что у меня нет доступа к экземпляру ' $ this-> httpUtils' .Кроме того, я просто хочу передать выполнение в 'AbstractAuthenticationListener' - я не хочу копировать и вставлять код из него в мои обработчики. – leek

+0

Я надеялся, что вы сможете найти хороший способ сделать это поскольку я застрял точно так же, как вы, с этим ;-) – DaveT

30
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); 
     } 
    } 
} 
+0

На этот код был дан ответ @elnur на аналогичный вопрос, который я сделал. –

+0

Это отличный ответ - извинения, не увидев ваш подобный вопрос. Тем не менее, я не собираюсь отмечать его как принятый, потому что я хочу только изменить ответ, когда текущий запрос является XMLHttpRequest. В противном случае я хочу, чтобы Symfony притворялся, будто я никогда не перехватывал. – leek

+1

Это легкая часть. Вы просто должны вернуть ответ json с содержимым, которое вам нравится, а затем прочитать его через jquery.Я думаю, что самая сложная (и, к сожалению, непризнанная) часть - это обработчик входа без аякса. –

3

я справилась с этим полностью с 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

+0

Хорошая идея, даже если она грязная (принятый ответ - лучший способ справиться с этим), это помогает. –

4

Если вы хотите поддержку FOS UserBundle форма ошибки, вы должны использовать:

$request->getSession()->set(SecurityContext::AUTHENTICATION_ERROR, $exception); 

вместо:

$request->getSession()->setFlash('error', $exception->getMessage()); 

В первом ответе.

(конечно помните о заголовке: используйте Symfony \ Component \ Security \ Core, \ SecurityContext;)

1

Я сделал узелок для новых пользователей, чтобы обеспечить в AJAX форму входа: https://github.com/Divi/AjaxLoginBundle

Вы просто необходимо заменить на form_login Аутентификация по ajax_form_login в security.yml.

Не стесняйтесь предлагать новую функцию в отслеживании вопросов Github!

0

Возможно, это не тот вопрос, который задал ОП, но я столкнулся с этим вопросом и подумал, что у других может быть такая же проблема, что и я.

Для тех, кто внедряет вход AJAX, используя метод, описанный в принятом ответе, и кто ТАКЖЕ использует AngularJS для выполнения запроса AJAX, это не будет работать по умолчанию. Угловой $http не устанавливает заголовки, используемые Symfony при вызове метода $request->isXmlHttpRequest(). Чтобы использовать этот метод, вам нужно установить соответствующий заголовок в запросе «Угловой». Это то, что я сделал, чтобы обойти эту проблему:

$http({ 
    method : 'POST', 
    url  : {{ path('login_check') }}, 
    data : data, 
    headers: {'X-Requested-With': 'XMLHttpRequest'} 
}) 

Перед тем, как использовать этот метод, имейте в виду, что этот заголовок не очень хорошо работает с CORS. См. this question