Selaa lähdekoodia

[Security/Http] Adds CSRF protection to the form-login

Johannes Schmitt 14 vuotta sitten
vanhempi
commit
dfd921822a

+ 26 - 0
src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php

@@ -11,6 +11,7 @@
 
 namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
 
+use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
 use Symfony\Component\DependencyInjection\DefinitionDecorator;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\Reference;
@@ -27,6 +28,8 @@ class FormLoginFactory extends AbstractFactory
     {
         $this->addOption('username_parameter', '_username');
         $this->addOption('password_parameter', '_password');
+        $this->addOption('csrf_parameter', '_csrf_token');
+        $this->addOption('csrf_page_id', 'form_login');
         $this->addOption('post_only', true);
     }
 
@@ -40,6 +43,15 @@ class FormLoginFactory extends AbstractFactory
         return 'form-login';
     }
 
+    public function addConfiguration(NodeBuilder $builder)
+    {
+        parent::addConfiguration($builder);
+
+        $builder
+            ->scalarNode('csrf_provider')->cannotBeEmpty()->end()
+        ;
+    }
+
     protected function getListenerId()
     {
         return 'security.authentication.listener.form';
@@ -57,6 +69,20 @@ class FormLoginFactory extends AbstractFactory
         return $provider;
     }
 
+    protected function createListener($container, $id, $config, $userProvider)
+    {
+        $listenerId = parent::createListener($container, $id, $config, $userProvider);
+
+        if (isset($config['csrf_provider'])) {
+            $container
+                ->getDefinition($listenerId)
+                ->addArgument(new Reference($config['csrf_provider']))
+            ;
+        }
+
+        return $listenerId;
+    }
+
     protected function createEntryPoint($container, $id, $config, $defaultEntryPoint)
     {
         $entryPointId = 'security.authentication.form_entry_point.'.$id;

+ 1 - 1
src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml

@@ -116,6 +116,6 @@
         <service id="security.firewall.context" class="%security.firewall.context.class%" abstract="true">
             <argument type="collection" />
             <argument type="service" id="security.exception_listener" />
-        </service>        
+        </service>
     </services>
 </container>

+ 12 - 0
src/Symfony/Component/Security/Core/Exception/InvalidCsrfTokenException.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace Symfony\Component\Security\Core\Exception;
+
+/**
+ * This exception is thrown when the csrf token is invalid.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class InvalidCsrfTokenException extends AuthenticationException
+{
+}

+ 20 - 5
src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php

@@ -11,15 +11,16 @@
 
 namespace Symfony\Component\Security\Http\Firewall;
 
+use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Log\LoggerInterface;
 use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
 use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
 use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
-
-use Symfony\Component\Security\Core\SecurityContextInterface;
 use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
-use Symfony\Component\HttpKernel\Log\LoggerInterface;
-use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
+use Symfony\Component\Security\Core\SecurityContextInterface;
 
 /**
  * UsernamePasswordFormAuthenticationListener is the default implementation of
@@ -29,16 +30,22 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  */
 class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationListener
 {
+    protected $csrfProvider;
+
     /**
      * {@inheritdoc}
      */
-    public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, $providerKey, array $options = array(), AuthenticationSuccessHandlerInterface $successHandler = null, AuthenticationFailureHandlerInterface $failureHandler = null, LoggerInterface $logger = null)
+    public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, $providerKey, array $options = array(), AuthenticationSuccessHandlerInterface $successHandler = null, AuthenticationFailureHandlerInterface $failureHandler = null, LoggerInterface $logger = null, CsrfProviderInterface $csrfProvider = null)
     {
         parent::__construct($securityContext, $authenticationManager, $sessionStrategy, $providerKey, array_merge(array(
             'username_parameter' => '_username',
             'password_parameter' => '_password',
+            'csrf_parameter'     => '_csrf_token',
+            'csrf_page_id'       => 'form_login',
             'post_only'          => true,
         ), $options), $successHandler, $failureHandler, $logger);
+
+        $this->csrfProvider = $csrfProvider;
     }
 
     /**
@@ -54,6 +61,14 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
             return null;
         }
 
+        if (null !== $this->csrfProvider) {
+            $csrfToken = $request->get($this->options['csrf_parameter']);
+
+            if (false === $this->csrfProvider->isTokenValid($this->options['csrf_page_id'], $csrfToken)) {
+                throw new InvalidCsrfTokenException('Invalid CSRF token.');
+            }
+        }
+
         $username = trim($request->get($this->options['username_parameter']));
         $password = $request->get($this->options['password_parameter']);