2016-04-15 7 views
1

So ziemlich alles ist im Titel.Zwei Firewalls mit zwei login_form und zwei verschiedenen AuthenticationSuccessHandlern, aber nur einer für beide

Ich komme hierher, um sicher zu sein, dass es sich um einen Fehler handelt, bevor es als Problem auf symfony/symfony gemeldet wird.

Ich habe folgendes security.yml:

security: 
    encoders: 
     Symfony\Component\Security\Core\User\User: plaintext 

    providers: 
     in_memory: 
      memory: 
       users: 
        chalasr: { password: chalasr, roles: [ 'ROLE_USER' ] } 

    firewalls: 
     admin: 
      pattern:   ^/admin 
      form_login: 
       provider:  in_memory 
       login_path:  /admin/login 
       check_path:  /admin/login_check 
       failure_path: null 
       success_handler: admin.authentication_success_handler 
      logout: 
       path:   /admin/logout 
      anonymous:   true 

     login_api: 
      pattern: ^/v1/login 
      stateless: true 
      anonymous: true 
      form_login: 
       provider: in_memory 
       check_path: /v1/login_check 
       require_previous_session: false 
       username_parameter: username 
       password_parameter: password 
       success_handler: api.authentication_success_handler 

     api: 
      pattern: ^/v1/ 
      stateless: true 
      lexik_jwt: ~ 

    access_control: 
     - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY } 
     - { path: ^/admin/, role: ROLE_USER } 
     - { path: ^/v1/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 
     - { path: ^/v1/, role: ROLE_USER } 

Wie Sie sehen können, habe ich zwei Login-Firewalls, eine für Routen mit ^/admin und eine für die mit ^/v1 passend entsprechen.
Die beiden form_login haben jeweilsauthentication_success_handler gesetzt.

Die api-Handler:

use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 

class ApiAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface 
{ 
    /** 
    * {@inheritdoc} 
    */ 
    public function onAuthenticationSuccess(Request $request, TokenInterface $token) 
     die('interecepted by api'); 
    } 
} 

Der Admin-Handler:

use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 

class AdminAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface 
{ 
    /** 
    * {@inheritdoc} 
    */ 
    public function onAuthenticationSuccess(Request $request, TokenInterface $token) 
    { 
     die('interecepted by admin'); 
    } 
} 

Dienstleistungen:

admin.authentication_success_handler: 
    class: AppBundle\EventListener\AdminAuthenticationSuccessHandler 
    tags: 
     - { name: kernel.event_listener, event: security.on_authentication_success, method: onAuthenticationSuccess } 

api.authentication_success_handler: 
    class: AppBundle\EventListener\ApiAuthenticationSuccessHandler 
    tags: 
     - { name: kernel.event_listener, event: security.on_authentication_success, method: onAuthenticationSuccess } 

Das Problem ist, dass die admin.authentication_success_handler die eine für beide Firewalls verwendet wird, ist es sagt überhaupt "admin abgefangen", für die api login route und für die admin login Route (Formular).

Mein erstes Ziel ist es auch Sachen zu machen, die völlig von der verwendeten Firewall abhängen, und dieses Verhalten verursacht einen Fehler in meiner Anwendung, wegen der Trennung admin/api.
Am Anfang, als ich den Fehler entdeckte, verwendete ich LexikJWTAuthenticationBundle und FOSUserBundle für die API und SonataAdminBundle (mit FOSUB) für den Admin, auf Symfony 2.8.
Dann habe ich auf 3.0 aufgerüstet und alle 3rd-Party-Security-Pakete entfernt, um jeden Zweifel daran zu vermeiden, und um sicher zu gehen, dass das von Symfony kommt. (Deshalb verwende ich einen einfachen Klartext-Encoder, so einfach wie möglich).

Bin ich richtig, wenn ich annehme, dass es ein Fehler ist? Oder mache ich einfach etwas falsch?

Weil das Ding offenbar noch nicht dokumentiert ist und wir viele verschiedene (funktionierende oder nicht) Implementierungen finden können.

EDIT

Hallo @tftd, danken die für Ihre Arbeit.

Es tut mir wirklich leid, aber ich habe auch meinen Code gekürzt, um die Frage lesbar zu machen, so dass die fehlenden anonymous: true in den beiden ein einfacher Tippfehler von meiner Seite ist, fügte ich sie hinzu.

In der Tat funktioniert die Implementierung in einem Projekt, das abgeschlossen ist, ist es in einem API und einem Admin getrennt, beide auf verschiedenen Hosts, die Admin-Firewall mit einem Login-Formular von FOSUserBundle und die API-Firewall mit einem einfachen/login_check Endpunkt, der eine JWT bereitstellt, dann senden Benutzer authentifizierte Anforderungen an /v1/* Routen mit dem Token als Header (Bearer).

Ich verbesserte auf 3.0, um in Phase zu sein, und ich verwendete eine einfache in_memory, um irgendwelche Zweifel über 3rd Party Bundles zu vermeiden (FOSUserBundle Erfolgsbehandlung, LexikJWT Erfolgsverarbeitung oder irgendetwas anderes, das die Ursache des Fehlers sein kann), und ich hoffe, ich vermeide wirklich jeden Zweifel darüber Sie.

Sie können meine einfache routing.yml (bedenken Sie, dass ich das Problem mit einem Beispiel, das nur verwendet wird, um zu sehen, wie der Login-Erfolg zu verhalten, es ist absolut nicht eine reale Welt, aber ich komme aus dem gleichen Verhalten in einer realen Welt).

// app/config/routing.yml 
fos_user: 
    prefix: /admin 
    resource: "@FOSUserBundle/Resources/config/routing/all.xml" 

api_login_check: 
    path: /v1/login_check 

Die Routen /admin/* von FOSUserBundle verwaltet werden, die das Anmeldeformular zur Verfügung stellen, ist mein Ziel, wenn die beiden Login Erfolg sehen nur abgefangen werden, und in der Tat sind sie.

Das Problem kam aus, dass meine zwei Zuhörer auf security.interactive_login zuhörte, es sah wie folgt aus:

class ApiAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface 
{ 
    public function onSecurityInteractiveLogin(InteractiveLoginEvent $event) 
    { 

     return $this->onAuthenticationSuccess($event->getRequest(), $event->getAuthenticationToken()); 
    } 

    public function onAuthenticationSuccess(Request $request, TokenInterface $token) 
    { 
     die('Interecepted by api'); 
    } 
} 

Und die beiden Dienste Erklärungen ausgesehen:

api.authentication_success_handler: 
    class: AppBundle\EventListener\AuthenticationSuccessHandlerApi ] 
    tags: 
     - { name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin } 

mit den Tags gesetzt, nur ein Listener wird ausgelöst.
Durch Entfernen der Tags werden die entsprechenden Handler ordnungsgemäß an jeder Firewall aufgerufen.

Schlussfolgerung, keine Notwendigkeit der Tag ein AuthenticationSuccessListener (außer aus einem anderen Grund als die onAuthenticationSuccess werfen), und wenn Sie es markieren, markieren sie als security.on_authentication_success Ereignis, nicht auf security.interactive_login, weil, egal des Handlers definiert in security.yml, die erste wird bei InteractiveLogin verwendet und die zweite wird komplett ignoriert.

Vielen Dank @Federico für die gute Antwort und nochmals danke @tdtd.

Antwort

2

Ich denke, dass das Problem liegt, wenn Sie AdminAuthenticationSuccessHandler 's onAuthenticationSuccess Methode zu dem Ereignis security.on_authentication_success abonnieren. Es wird jedes Mal ausgelöst, wenn ein Benutzer von einem Anbieter authentifiziert wird, egal welche. Da Sie zuerst admin.authentication_success_handler registrieren, wird es zuerst ausgeführt.

Ich bin mir nicht ganz sicher, dass, weil der Ereignisname ist, sondern versucht, Ihre Service-Definition mit

admin.authentication_success_handler: 
    class: AppBundle\EventListener\AdminAuthenticationSuccessHandler 

api.authentication_success_handler: 
    class: AppBundle\EventListener\ApiAuthenticationSuccessHandler 
+0

Das Problem war, weil ich die 'security.on_interactive_login' verwendet habe, umAuthenticationSuccess manuell aufzurufen (siehe meine Bearbeitung). Wenn ich durch "security.on_authentication_success" wie in meiner Frage ersetzen, funktioniert alles gut. Wenn ich die Tags entferne, funktioniert alles gut, also gebe ich Ihnen den Punkt! t Danke – chalasr

1

Ich bin mir nicht ganz sicher, zu ändern, warum Ihre Konfiguration nicht „Schleife nicht anzeigt "Fehler. Gemäß der official documentation Ihr login Pfad sollte für anonyme Benutzer zugänglich sein und sollte unter Ihrer Firewall sein. In der aktuellen Situation haben Sie die login Pfad unter /admin/, die ROLE_USER erfordert.

Ihre Konfiguration eher etwas sollte wie folgt aussehen:

Sicherheit:

encoders: 
    Symfony\Component\Security\Core\User\User: plaintext 

providers: 
    in_memory: 
     memory: 
      users: 
       user: { password: password, roles: [ 'ROLE_USER' ] } 

firewalls: 
    dev: 
     pattern: ^/(_(profiler|wdt)|css|images|js)/ 
     security: false 

    admin_login: 
     anonymous: ~ 
     pattern: ^/admin/login$ 

    admin: 
     pattern:  ^/admin 
     provider:  in_memory 
     form_login: 
      login_path:    /admin/login 
      check_path:    /admin/login_check 
      default_target_path: /admin/ 
      success_handler:  admin.authentication_success_handler 
     logout: 
      path:  /admin/logout 
      target:  /admin/login 

    api: 
     pattern:    ^/v1 
     stateless:   true 
     form_login: 
      provider:  in_memory 
      check_path:  /v1/login_check 
      success_handler: api.authentication_success_handler 
      require_previous_session: false 


access_control: 
    - { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 
    - { path: ^/admin, role: ROLE_USER } 
    - { path: ^/v1/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } 
    - { path: ^/v1, role: ROLE_USER } 

In Ihrem routing.yml stellen Sie sicher, sowie ändern die Links in Ihren Vorlagen:

admin_logout: 
    path: /admin/logout 

admin_login_check: 
    path: /admin/login_check 

api_login_check: 
    path: /v1/login_check 

Die SecurityController:

<?php 

namespace AppBundle\Controller; 

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; 
use Symfony\Bundle\FrameworkBundle\Controller\Controller; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\Routing\Annotation\Route; 

class SecurityController extends Controller 
{ 
    /** 
    * @Route("/login",  name="api_login") 
    * @Route("/admin/login", name="admin_login") 
    * @Template("::login.html.twig") 
    */ 
    public function loginAction(Request $request) 
    { 
     $authenticationUtils = $this->get('security.authentication_utils'); 

     // get the login error if there is one 
     $error = $authenticationUtils->getLastAuthenticationError(); 

     // last username entered by the user 
     $lastUsername = $authenticationUtils->getLastUsername(); 

     return array(
      // last username entered by the user 
      'last_username' => $lastUsername, 
      'error'   => $error, 
      'login_path' => $request->get('_route') == 'admin_login' ? '/admin/login_check' : '/v1/login_check' 
     ); 
    } 
} 

?> 

Die app/Resources/views/login.html.twig Vorlage:

{% extends '::base.html.twig' %} 

{% block body %} 
    {% if error %} 
     <div>{{ error.messageKey|trans(error.messageData, 'security') }}</div> 
    {% endif %} 

    <form action="{{ login_path }}" method="post"> 
     <label for="username">Username:</label> 
     <input type="text" id="username" name="_username" value="{{ last_username }}" /> 

     <label for="password">Password:</label> 
     <input type="password" id="password" name="_password" /> 

     <button type="submit">login</button> 
    </form> 
{% endblock %} 

-Service und Erfolg Handler Definitionen sind absolut identisch zu dem, was du gepostet hast.

Der obige Code ist eher ein Proof of Concept, um Ihnen zu zeigen, dass die verschiedenen Firewalls problemlos mit verschiedenen success_handler funktionieren. Dies ist so, weil Sie in jeder Firewall die success_handler definiert haben, die Sie aufrufen möchten, wenn eine erfolgreiche Authentifizierung durchgeführt wurde. Außerdem brauchen Sie im wirklichen Leben nicht /v1/login, weil die POST Anfrage an Ihre API eine Kopfzeile mit dem Authentifizierungs-/API-Schlüssel enthalten sollte.

Wie für Ihre API-Implementierung - Ich habe das Gefühl, Sie Ansatz könnte falsch sein. Vor einiger Zeit benutzte ich ApiKeyAuthenticator Implementierung, um REST APIs zu erstellen. Es ist eine gute "Basis", um zu verstehen, wie man API als Implementierung startet. Mit neueren Versionen von Symfony (3.0+) können Sie jedoch Guard Authentication verwenden, was etwas einfacher scheint.

+0

Hi @tftd, ich habe meine Frage bearbeitet, das Problem kam von meiner Seite. – chalasr

+1

Jetzt sehe ich. Gut, ich könnte trotzdem helfen! :) – tftd

Verwandte Themen