Symfony 3.4如何替换默认认证监听器集成Google reCAPTCHA?
Hey there! Let's tackle this reCAPTCHA integration with Symfony 3.4's security system. Since you've already got a custom authentication listener ready and know how to validate the reCAPTCHA, here are two solid approaches to get this working without relying on a third-party bundle:
Option 1: Replace the Default Form Authentication Listener (Symfony 3.x Compatible)
Symfony 3 removed the direct parameter override for the form authentication listener class, but you can use a Compiler Pass to swap out the default listener with your custom one. Here's how:
Step 1: Create a Compiler Pass Class
This pass will modify the default form authentication listener service to use your custom class:
// src/MyBundle/DependencyInjection/Compiler/MyAuthenticationListenerCompilerPass.php namespace MyBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; class MyAuthenticationListenerCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { // Check if the default form listener service exists if ($container->hasDefinition('security.authentication.listener.form')) { $listenerDef = $container->getDefinition('security.authentication.listener.form'); // Replace with your custom listener class $listenerDef->setClass('MyBundle\EventListener\MyAuthenticationListener'); } } }
Step 2: Register the Compiler Pass in Your Bundle
Add the pass to your bundle's build method to activate it:
// src/MyBundle/MyBundle.php namespace MyBundle; use MyBundle\DependencyInjection\Compiler\MyAuthenticationListenerCompilerPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class MyBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); $container->addCompilerPass(new MyAuthenticationListenerCompilerPass()); } }
This will ensure your MyAuthenticationListener is used instead of the default UsernamePasswordFormAuthenticationListener for all form-based authentication firewalls.
Option 2: Add a Pre-Authentication Request Listener (Simpler Approach)
If you don't want to replace the entire listener, you can add a kernel.request listener that validates reCAPTCHA before the default authentication logic runs. This is quicker to implement:
Step 1: Create the Recaptcha Validation Listener
This listener will check the reCAPTCHA response only on your login check path:
// src/MyBundle/EventListener/RecaptchaValidationListener.php namespace MyBundle\EventListener; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Security; class RecaptchaValidationListener { private $router; private $recaptchaSecretKey; public function __construct(RouterInterface $router, string $recaptchaSecretKey) { $this->router = $router; $this->recaptchaSecretKey = $recaptchaSecretKey; } public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); // Only handle POST requests to your login check path if ($request->getPathInfo() !== '/login_check' || !$request->isMethod('POST')) { return; } // Validate reCAPTCHA response $recaptchaResponse = $request->request->get('g-recaptcha-response'); if (!$this->isRecaptchaValid($recaptchaResponse)) { // Store error and redirect back to login page $session = $request->getSession(); $session->set(Security::AUTHENTICATION_ERROR, new AuthenticationException('Invalid reCAPTCHA')); $session->set(Security::LAST_USERNAME, $request->request->get('_username')); $event->setResponse(new RedirectResponse($this->router->generate('login'))); } } private function isRecaptchaValid(string $response): bool { // Your existing reCAPTCHA validation logic here $url = 'https://www.google.com/recaptcha/api/siteverify'; $data = http_build_query([ 'secret' => $this->recaptchaSecretKey, 'response' => $response, 'remoteip' => $request->getClientIp() ]); $context = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => 'Content-Type: application/x-www-form-urlencoded', 'content' => $data ] ]); $result = json_decode(file_get_contents($url, false, $context), true); return $result['success'] ?? false; } }
Step 2: Register the Listener in Services.yml
Make sure to set a high priority so it runs before the default authentication listener:
# app/config/services.yml services: my_bundle.recaptcha_validation_listener: class: MyBundle\EventListener\RecaptchaValidationListener arguments: - '@router' - '%your_recaptcha_secret_key%' # Define this parameter in config.yml tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 10 }
Step 3: Add reCAPTCHA to Your Login Form
Update your Twig login template to include the reCAPTCHA widget:
{# templates/security/login.html.twig #} <form action="{{ path('login_check') }}" method="post"> <!-- Username & Password Fields --> <div> <label for="username">Username:</label> <input type="text" id="username" name="_username" value="{{ last_username }}"> </div> <div> <label for="password">Password:</label> <input type="password" id="password" name="_password"> </div> <!-- reCAPTCHA Widget --> <div class="g-recaptcha" data-sitekey="%your_recaptcha_site_key%"></div> <button type="submit">Login</button> <!-- Display Errors --> {% if error %} <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div> {% endif %} </form> <script src="https://www.google.com/recaptcha/api.js" async defer></script>
Which Option to Choose?
- Option 1 is ideal if you want full control over the authentication flow and integrate reCAPTCHA directly into the security layer.
- Option 2 is simpler and faster to implement, perfect if you just need to add reCAPTCHA validation without modifying core authentication logic.
内容的提问来源于stack exchange,提问作者user1923631

