2011-12-22 16 views
40

Mam przykład, w którym próbuję utworzyć login AJAX za pomocą Symfony2 i FOSUserBundle. Ustawię własne success_handler i failure_handler pod form_login w moim pliku .Symfony2 AJAX Login

Oto klasa:

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; 
     } 
    } 
} 

Działa to doskonale nadaje się do obsługi zarówno sukces i udało AJAX prób logowania. Jednak po włączeniu - nie mogę się zalogować przy użyciu standardowej metody POST (nie AJAX). I pojawia się następujący błąd:

Catchable Fatal Error: Argument 1 passed to Symfony\Component\HttpKernel\Event\GetResponseEvent::setResponse() must be an instance of Symfony\Component\HttpFoundation\Response, null given

chciałbym dla moich onAuthenticationSuccess i onAuthenticationFailure zadajnikami być wykonywane tylko przez XMLHttpRequests (żądań AJAX) i po prostu oddać wykonanie z powrotem do oryginalnego przewodnika czy nie.

Czy istnieje sposób, aby to zrobić?

TL; DR Chcę, aby AJAX żądał prób logowania, aby zwrócić odpowiedź JSON na sukces i porażkę, ale chcę, aby nie wpłynęła na standardowe logowanie poprzez formularz POST.

+1

Mam dokładnie taki sam problem. I napisali pytanie tutaj: http://stackoverflow.com/questions/8654265/calling-default-handler-from-a-custom-authentication-handler ale nie ma odpowiedzi na teraz :( –

+1

Jest to świetny artykuł wyjaśniający jak to zrobić to tutaj: http://www.webtipblog.com/adding-an-ajax-login-form-to-a-symfony-project/ – joe42

Odpowiedz

48

David's answer jest dobry, ale brakuje mu trochę szczegółów dla nowicjuszy - więc to jest wypełnianie pustych miejsc.

Oprócz tworzenia usługi Uwierzytelnianie uwierzytelniania należy skonfigurować ją jako usługę, korzystając z konfiguracji usługi w pakiecie, w którym utworzono procedurę obsługi. Domyślna generacja pakietów tworzy plik xml, ale ja wolę yml. Oto przykład usług.Plik 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' } 

trzeba by zmodyfikować rozszerzenie wstrzykiwanie zależności wiązki używać yml zamiast XML tak:

#src/Vendor/BundleName/DependencyInjection/BundleExtension.php 

$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 
$loader->load('services.yml'); 

Następnie w konfiguracji zabezpieczeń swojej aplikacji skonfigurowaniu odniesienia do authentication_handler obsługa po prostu zdefiniowane:

# 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

W konfiguracji services.yml jest literówka. Przed kanałem wymagany jest przecinek (,) - {nazwa: "monolog.logger", kanał: "bezpieczeństwo"} –

+0

Dzięki temu pomogła wypełnić luki z Davids Odpowiedź! –

2

Musisz zwrócić obiekt Response w obu przypadkach (Ajax lub nie). Dodaj "coś innego" i jesteś gotowy.

Domyślna implementacja jest:

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

w AbstractAuthenticationListener::onSuccess

+0

Problem polega na tym, że nie mam żadnego dostępu do instancji ' $ this-> httpUtils' .Ponownie, po prostu chcę przekazać wykonanie z powrotem do 'AbstractAuthenticationListener' - Nie chcę kopiować i wklejać kodu z tego do moich procedur obsługi – leek

+0

Miałem nadzieję, że możesz znaleźć dobry sposób na zrobienie tego ponieważ utknąłem dokładnie tak jak ty z tym ;-) – 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

Ten kod został odpowiedział @elnur na podobne pytanie, które zrobiłem. –

+0

To jest świetna odpowiedź - przepraszam, że nie widziałem twojego podobnego pytania. Jednak nie zamierzam oznaczyć go jako zaakceptowany, ponieważ chcę zmodyfikować odpowiedź tylko wtedy, gdy obecne żądanie jest XMLHttpRequest - w przeciwnym razie chcę, aby Symfony udawał, że nigdy nie przechwyciłem. – leek

+1

To jest łatwa część. Powinieneś tylko zwrócić odpowiedź JSON z treścią, którą lubisz, a następnie przeczytać ją za pomocą jquery.Myślę, że najtrudniejszą (i niestety nieumieszczoną) częścią jest obsługa nie-ajaxowa. –

3

I obchodzić to całkowicie z 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; 
     }); 
    } 

Oto klasa AjaxFormDialog. Niestety nie przeportowałem go do wtyczki jQuery już teraz ... https://gist.github.com/1601803

+0

Dobry pomysł, nawet jeśli jest brudny (zaakceptowana odpowiedź jest najlepszym sposobem na poradzenie sobie z tym), pomaga. –

4

Jeśli chcesz wsparcie błędzie forma FOS UserBundle, należy użyć:

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

zamiast:

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

W pierwszej odpowiedzi.

(oczywiście pamiętać o nagłówku: używać Symfony \ Komponent \ Security \ rdzenia \ SecurityContext;)

1

zrobiłem mały pakiet dla nowych użytkowników, aby zapewnić AJAX formularz logowania: https://github.com/Divi/AjaxLoginBundle

Po prostu musi zastąpić logowanie_formulacyjne uwierzytelnianie przez ajax_form_login w security.yml.

Zapraszam zaproponować nową funkcję w emisyjnej trackera GitHub!

0

To nie może być to, co zapytał OP, ale natknąłem się na to pytanie i myślał inni mogą mieć ten sam problem, że ja.

Dla tych, którzy wdrażają logowanie AJAX przy użyciu metody opisanej w zaakceptowanej odpowiedzi i którzy również używają AngularJS do wykonania żądania AJAX, domyślnie to nie działa. Angular's $http nie ustawia nagłówków używanych przez Symfony podczas wywoływania metody $request->isXmlHttpRequest(). Aby skorzystać z tej metody, musisz ustawić odpowiedni nagłówek w żądaniu kątowym. To, co zrobiłem, aby obejść problem:

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

Przed użyciem tej metody, należy pamiętać, że nagłówek nie działa dobrze z CORS. Zobacz this question