43

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

这是课程:

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

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

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

有没有办法做到这一点?

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

4

7 回答 7

50

大卫的回答很好,但是对于新手来说缺少一点细节 - 所以这是为了填补空白。

除了创建 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
于 2012-02-27T00:10:31.093 回答
31
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);
        }
    }
}
于 2011-12-29T15:25:50.900 回答
4

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

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

代替:

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

在第一个答案中。

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

于 2013-02-06T22:17:36.077 回答
3

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

于 2012-01-05T14:44:19.490 回答
2

您必须在两种情况下都返回一个 Response 对象(Ajax 与否)。添加一个“其他”,你就可以开始了。

默认实现是:

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

AbstractAuthenticationListener::onSuccess

于 2011-12-23T06:05:01.747 回答
1

我为新用户制作了一个小包以提供 AJAX 登录表单:https ://github.com/Divi/AjaxLoginBundle

您只需将security.yml中的ajax_form_login替换为form_login身份验证。

随意在 Github 问题跟踪器中推荐新功能!

于 2013-02-28T21:00:38.103 回答
0

这可能不是 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。看到这个问题

于 2014-05-29T18:01:43.310 回答