У меня есть пример, где я пытаюсь создать логин AJAX, используя Symfony2 и FOSUserBundle. Я устанавливаю свой собственный success_handler
и failure_handler
в form_login
в файле security.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
Я бы хотел, чтобы мои onAuthenticationSuccess
и onAuthenticationFailure
только для XmlHttpRequests (запросы AJAX) и просто передавали выполнение обратно исходному обработчику, если нет.
Есть ли способ сделать это?
TL; DR Я хочу, чтобы AJAX запросил попытки входа в систему вернуть ответ JSON для успеха и неудачи, но я хочу, чтобы он не влиял на стандартный вход через форму POST.
Ответ Дэвида хорош, но для новичков ему не хватает деталей, поэтому нужно заполнить пробелы.
В дополнение к созданию 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
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); } } }
Если вам нужна поддержка ошибок формы FOS UserBundle, вы должны использовать:
$request->getSession()->set(SecurityContext::AUTHENTICATION_ERROR, $exception);
вместо:
$request->getSession()->setFlash('error', $exception->getMessage());
В первом ответе.
(конечно, помните о заголовке: используйте Symfony \ Component \ Security \ Core \ SecurityContext;)
Я обработал это полностью с помощью 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
Вы должны вернуть объект Response в обоих случаях (Ajax или нет). Добавьте «else», и вам хорошо идти.
Реализация по умолчанию:
$response = $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
в AbstractAuthenticationListener::onSuccess
Я сделал небольшой пакет для новых пользователей, чтобы предоставить форму входа AJAX: https://github.com/Divi/AjaxLoginBundle
Вам просто нужно заменить на form_login аутентификацию ajax_form_login в security.yml .
Не стесняйтесь предлагать новую функцию в трекере Github!
Возможно, это не то, что задал ОП, но я столкнулся с этим вопросом и подумал, что у других может быть такая же проблема, что и я.
Для тех, кто внедряет вход AJAX, используя метод, описанный в принятом ответе, и кто ТАКЖЕ использует AngularJS для выполнения запроса AJAX, это не будет работать по умолчанию. $http
не задает заголовки, которые Symfony использует при вызове метода $request->isXmlHttpRequest()
. Чтобы использовать этот метод, вам нужно установить соответствующий заголовок в запросе «Угловой». Это то, что я сделал, чтобы решить проблему:
$http({ method : 'POST', url : {{ path('login_check') }}, data : data, headers: {'X-Requested-With': 'XMLHttpRequest'} })
Прежде чем использовать этот метод, имейте в виду, что этот заголовок не работает с CORS. См. Этот вопрос