简体   繁体   English

Symfony2 AJAX登录

[英]Symfony2 AJAX Login

I have an example where I am trying to create an AJAX login using Symfony2 and FOSUserBundle. 我有一个示例,尝试使用Symfony2和FOSUserBundle创建AJAX登录。 I am setting my own success_handler and failure_handler under form_login in my security.yml file. 我设置我自己success_handlerfailure_handlerform_loginsecurity.yml文件。

Here is the class: 这是课程:

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

This works great for handling both successful and failed AJAX login attempts. 这对于处理成功和失败的AJAX登录尝试都非常有用。 However, when enabled - I am unable to login via the standard form POST method (non-AJAX). 但是,启用后-我无法通过标准表单POST方法(非AJAX)登录。 I receive the following error: 我收到以下错误:

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

I'd like for my onAuthenticationSuccess and onAuthenticationFailure overrides to only be executed for XmlHttpRequests (AJAX requests) and to simply hand the execution back to the original handler if not. 我希望onAuthenticationSuccessonAuthenticationFailure重写仅对XmlHttpRequests(AJAX请求)执行,如果没有,则将执行交还给原始处理程序。

Is there a way to do this? 有没有办法做到这一点?

TL;DR I want AJAX requested login attempts to return a JSON response for success and failure but I want it to not affect standard login via form POST. TL; DR我希望AJAX请求登录尝试返回JSON响应以表示成功和失败,但我希望它不影响通过POST表单进行的标准登录。

David's answer is good, but it's lacking a little detail for newbs - so this is to fill in the blanks. David的回答很好,但是对于新手来说,它缺少一些细节-因此,这是要填补空白。

In addition to creating the AuthenticationHandler you'll need to set it up as a service using the service configuration in the bundle where you created the handler. 除了创建AuthenticationHandler之外,还需要使用创建处理程序的捆绑软件中的服务配置将其设置为服务。 The default bundle generation creates an xml file, but I prefer yml. 默认的捆绑软件生成将创建一个xml文件,但我更喜欢yml。 Here's an example services.yml file: 这是一个示例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' }

You'd need to modify the DependencyInjection bundle extension to use yml instead of xml like so: 您需要修改DependencyInjection捆绑包扩展以使用yml而不是xml,如下所示:

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

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

Then in your app's security configuration you set up the references to the authentication_handler service you just defined: 然后在应用程序的安全性配置中,设置对您刚定义的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
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);
        }
    }
}

If you want the FOS UserBundle form error support, you must use: 如果需要FOS UserBundle表单错误支持,则必须使用:

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

instead of: 代替:

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

In the first answer. 在第一个答案。

(of course remember about the header: use Symfony\\Component\\Security\\Core\\SecurityContext;) (当然要记住标题:使用Symfony \\ Component \\ Security \\ Core \\ SecurityContext;)

I handled this entirely with javascript: 我完全使用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;
        });
    }

Here is the AjaxFormDialog class. 这是AjaxFormDialog类。 Unfortunately I have not ported it to a jQuery plugin by now... https://gist.github.com/1601803 不幸的是,到目前为止我还没有将其移植到jQuery插件... https://gist.github.com/1601803

You must return a Response object in both case (Ajax or not). 在两种情况下(无论是否为Ajax),都必须返回一个Response对象。 Add an `else' and you're good to go. 添加一个“ else”,就可以了。

The default implementation is: 默认实现为:

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

in AbstractAuthenticationListener::onSuccess AbstractAuthenticationListener::onSuccess

I made a little bundle for new users to provide an AJAX login form : https://github.com/Divi/AjaxLoginBundle 我为新用户提供了一些捆绑包,以提供AJAX登录表单: https : //github.com/Divi/AjaxLoginBundle

You just have to replace to form_login authentication by ajax_form_login in the security.yml . 您只需要在security.yml中用ajax_form_login替换为form_login身份验证。

Feel free to suggest new feature in the Github issue tracker ! 欢迎在Github问题追踪器中建议新功能!

This may not be what the OP asked, but I came across this question, and thought others might have the same problem that I did. 这可能不是OP所要求的,但是我遇到了这个问题,并认为其他人可能也遇到了与我同样的问题。

For those who are implementing an AJAX login using the method that is described in the accepted answer and who are ALSO using AngularJS to perform the AJAX request, this won't work by default. 对于那些使用接受的答案中描述的方法实现AJAX登录并且还使用AngularJS执行AJAX请求的用户,默认情况下将不起作用。 Angular's $http does not set the headers that Symfony is using when calling the $request->isXmlHttpRequest() method. Angular的$http不会设置调用$request->isXmlHttpRequest()方法时Symfony使用的标头。 In order to use this method, you need to set the appropriate header in the Angular request. 为了使用此方法,您需要在Angular请求中设置适当的标头。 This is what I did to get around the problem: 这是我解决该问题的方法:

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

Before you use this method, be aware that this header does not work well with CORS. 使用此方法之前,请注意,此标头不适用于CORS。 See this question 看到这个问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM