2011-12-22 19 views
40

Ho un esempio in cui sto cercando di creare un accesso AJAX utilizzando Symfony2 e FOSUserBundle. Sto impostando il mio success_handler e failure_handler sotto form_login nel mio file security.yml.Symfony2 Login AJAX

Qui è la classe:

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

Questa grande opera per la gestione sia di successo e non è riuscito AJAX tentativi di accesso. Tuttavia, se abilitato, non riesco ad accedere tramite il metodo POST del modulo standard (non AJAX). Ricevo il seguente errore:

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

vorrei per i miei onAuthenticationSuccess e onAuthenticationFailure sostituzioni per essere eseguita solo per XMLHttpRequests (richieste AJAX) ed a portata di mano semplicemente l'esecuzione torna al gestore originale in caso contrario.

C'è un modo per farlo?

TL; DR Desidero che AJAX richieda i tentativi di accesso per restituire una risposta JSON per il successo e l'insuccesso ma voglio che non influenzi l'accesso standard tramite il modulo POST.

+1

Ho lo stesso problema esatto. Ho postato una domanda qui: http://stackoverflow.com/questions/8654265/calling-default-handler-from-a-custom-authentication-handler ma nessuna risposta per ora :( –

+1

C'è un grande articolo che spiega come fare questo qui: http://www.webtipblog.com/adding-an-ajax-login-form-to-a-symfony-project/ – joe42

risposta

48

David's answer è buono, ma manca un piccolo dettaglio per i newb, quindi questo è per riempire gli spazi vuoti.

Oltre a creare AuthenticationHandler, è necessario configurarlo come servizio utilizzando la configurazione del servizio nel pacchetto in cui è stato creato il gestore. La generazione di bundle predefinita crea un file xml, ma io preferisco yml. Ecco un esempio di servizi.File 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' } 

avresti bisogno di modificare l'estensione DependencyInjection fascio di utilizzare yml invece di XML in questo modo:

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

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

Poi nella configurazione di sicurezza della vostra applicazione si configura il riferimenti al authentication_handler servizio che appena definito:

# 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

C'è un errore di battitura nella configurazione services.yml. Una virgola (,) è richiesta prima del canale - {name: 'monolog.logger', canale: 'security'} –

+0

Grazie per aver contribuito a colmare le lacune di Davids Risposta! –

2

È necessario restituire un oggetto Response in entrambi i casi (Ajax o no). Aggiungi un "altro" e sei a posto.

L'implementazione di default è:

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

in AbstractAuthenticationListener::onSuccess

+0

Il problema con questo è che non ho alcun accesso all'istanza di ' $ this-> httpUtils'. Inoltre, voglio solo passare l'esecuzione a 'AbstractAuthenticationListener' - Non voglio copiare e incollare il codice da esso nei miei gestori. – leek

+0

Spero che tu possa trovare un buon modo per farlo come sono bloccato esattamente come te con questo ;-) – 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

Questo codice è stato risposto da @elnur a una domanda simile che ho fatto. –

+0

Questa è un'ottima risposta - scuse non avendo visto la tua domanda simile. Tuttavia, non ho intenzione di contrassegnarlo come accettato perché voglio solo modificare la risposta quando la richiesta corrente è XMLHttpRequest - altrimenti, voglio che Symfony faccia finta di non averlo mai intercettato. – leek

+1

Questa è la parte facile. Dovresti solo restituire una risposta json con i contenuti che ti piacciono, e poi leggerla attraverso jquery.Penso che la parte più difficile (e tristemente incompleta) sia il gestore di accesso non Ajax. –

3

ho gestito questo interamente con 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; 
     }); 
    } 

Ecco la classe AjaxFormDialog. Sfortunatamente non l'ho ancora portato su un plugin jQuery ... https://gist.github.com/1601803

+0

Buona idea, anche se è sporca (la risposta accettata è il modo migliore per gestirlo), aiuta. –

4

Se si desidera che il supporto FOS UserBundle errore di forma, è necessario utilizzare:

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

invece di:

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

Nella prima risposta.

(ovviamente c'é da sapere su l'intestazione: use Symfony \ Component \ Security \ Core \ SecurityContext;)

1

ho fatto un piccolo fagotto per i nuovi utenti di fornire un form di login AJAX: https://github.com/Divi/AjaxLoginBundle

Basta è necessario sostituire l'autenticazione form_login tramite ajax_form_login nel security.yml.

Sentiti libero di suggerire una nuova funzione nel tracker dei problemi di Github!

0

Questo potrebbe non essere quello che l'OP ha chiesto, ma ho trovato questa domanda e ho pensato che gli altri potessero avere lo stesso problema che ho fatto io.

Per coloro che stanno implementando un accesso AJAX utilizzando il metodo descritto nella risposta accettata e che sono ANCHE utilizzando AngularJS per eseguire la richiesta AJAX, questo non funzionerà per impostazione predefinita. Angular's $http non imposta le intestazioni utilizzate da Symfony quando chiama il metodo $request->isXmlHttpRequest(). Per utilizzare questo metodo, è necessario impostare l'intestazione appropriata nella richiesta Angolare. Questo è quello che ho fatto per aggirare il problema:

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

Prima di utilizzare questo metodo, essere consapevoli che questo colpo di testa non funziona bene con CORS. Vedi this question

Problemi correlati