php-Symfony2 AJAX登录

我有一个示例,尝试使用Symfony2和FOSUserBundle创建AJAX登录。 我在security.yml文件中的onAuthenticationFailure下设置了自己的success_handleronAuthenticationSuccess

这是课程:

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)登录。 我收到以下错误:

onAuthenticationSuccess

我希望将我的onAuthenticationSuccessonAuthenticationFailure重写仅对XmlHttpRequests(AJAX请求)执行,如果没有,则将执行交还给原始处理程序。

有没有办法做到这一点?

TL; DR我希望AJAX请求登录尝试返回JSON响应以表示成功和失败,但是我希望它不影响通过POST表单进行的标准登录。

leek asked 2020-07-31T01:26:49Z
7个解决方案
50 votes

David的回答很好,但是对于新手来说,它缺少一些细节-因此,这是要填补空白。

除了创建AuthenticationHandler之外,还需要使用创建处理程序的捆绑软件中的服务配置将其设置为服务。 默认的捆绑软件生成将创建一个xml文件,但是我更喜欢yml。 这是一个示例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' }

您需要修改DependencyInjection捆绑包扩展以使用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
semateos answered 2020-07-31T01:27:17Z
31 votes
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);
        }
    }
}
David Morales answered 2020-07-31T01:27:32Z
4 votes

如果需要FOS UserBundle表单错误支持,则必须使用:

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

代替:

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

在第一个答案。

(当然请记住标题:使用Symfony \ Component \ Security \ Core \ SecurityContext;)

user2048716 answered 2020-07-31T01:28:05Z
3 votes

我完全使用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]

stoefln answered 2020-07-31T01:28:29Z
2 votes

在两种情况下(无论是否为Ajax),都必须返回一个Response对象。 添加一个“ else”,就可以了。

默认实现为:

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

AbstractAuthenticationListener::onSuccess

DaveT answered 2020-07-31T01:28:58Z
1 votes

我为新用户提供了一些捆绑包,以提供AJAX登录表单:[https://github.com/Divi/AjaxLoginBundle]

您只需要在security.yml中用ajax_form_login替换为form_login身份验证。

欢迎在Github问题追踪器中建议新功能!

Divi answered 2020-07-31T01:29:27Z
0 votes

这可能不是OP所要求的,但是我遇到了这个问题,并认为其他人可能也遇到了与我相同的问题。

对于那些使用接受的答案中描述的方法实现AJAX登录并且还使用AngularJS执行AJAX请求的用户,默认情况下将不起作用。 Angular的$http不会设置Symfony在调用$request->isXmlHttpRequest()方法时使用的标头。 为了使用此方法,您需要在Angular请求中设置适当的标头。 这是我解决该问题的方法:

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

使用此方法之前,请注意,此标头不适用于CORS。 看到这个问题

Sehael answered 2020-07-31T01:29:57Z
translate from https://stackoverflow.com:/questions/8607212/symfony2-ajax-login