2012-10-05 65 views
5

He creado un votante personalizado que deniega el acceso a mi API si la solicitud no contiene un encabezado de autenticación válido. Se basa en una combinación de dos entradas de libros de cocina: y http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.htmlVoter causó el error 500 en lugar de 403

<?php 

namespace Acme\RestBundle\Security\Authorization\Voter; 

use Symfony\Component\DependencyInjection\ContainerInterface; 
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; 
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 
use Symfony\Component\Security\Core\Exception\AuthenticationException; 
use Symfony\Component\Security\Core\Exception\NonceExpiredException; 
use Monolog\Logger; 

class ApiClientVoter implements VoterInterface 
{ 
    protected $container; 
    protected $nonceCacheDir; 

    /** 
    * We inject the full service container due to scope issues when 
    * injecting the request. 
    * 
    * @param ContainerInterface $container 
    * @param string $nonceCacheDir 
    */ 
    public function __construct(ContainerInterface $container, $nonceCacheDir) 
    { 
     $this->container  = $container; 
     $this->nonceCacheDir = $nonceCacheDir; 
    } 

    public function supportsAttribute($attribute) 
    { 
     // we won't check against a user attribute, so we return true 
     return true; 
    } 

    public function supportsClass($class) 
    { 
     // our voter supports all type of token classes, so we return true 
     return true; 
    } 

    public function vote(TokenInterface $token, $object, array $attributes) 
    { 
     if ($this->authenticate()) { 
      return VoterInterface::ACCESS_ABSTAIN; 
     } 

     return VoterInterface::ACCESS_DENIED; 
    } 


    /** 
    * Checks for an authentication header in the request and confirms 
    * the client is valid. 
    * 
    * @return bool 
    */ 
    protected function authenticate() 
    { 
     $request = $this->container->get('request'); 

     if ($request->headers->has('x-acme-auth')) { 

      $authRegex = '/ApiKey="([^"]+)", ApiDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/'; 

      if (preg_match($authRegex, $request->headers->get('x-acme-auth'), $matches)) { 
       $apiClient = $this->container->get('in_memory_user_provider')->loadUserByUsername($matches[1]); 

       if ($apiClient && $this->validateDigest($matches[2], $matches[3], $matches[4], $apiClient->getPassword())) { 
        return true; 
       } 
      } 
     } else { 
      $this->container->get('logger')->err('no x-acme-auth header present in request'); 
     } 

     return false; 
    } 

    /** 
    * Performs checks to prevent replay attacks and to validate 
    * digest against a known client. 
    * 
    * @param string $digest 
    * @param string $nonce 
    * @param string $created 
    * @param string $secret 
    * @return bool 
    * @throws AuthenticationException 
    * @throws NonceExpiredException 
    */ 
    protected function validateDigest($digest, $nonce, $created, $secret) 
    { 
     // Expire timestamp after 5 minutes 
     if (time() - strtotime($created) > 300) { 
      $this->container->get('logger')->err('Timestamp expired'); 

      return false; 
     } 

     if (!is_dir($this->nonceCacheDir)) { 
      mkdir($this->nonceCacheDir, 0777, true); 
     } 

     // Validate nonce is unique within 5 minutes 
     if (file_exists($this->nonceCacheDir.'/'.$nonce) && file_get_contents($this->nonceCacheDir.'/'.$nonce) + 300 > time()) { 
      $this->container->get('logger')->err('Previously used nonce detected'); 

      return false; 
     } 

     file_put_contents($this->nonceCacheDir.'/'.$nonce, time()); 

     // Validate Secret 
     $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true)); 

     return $digest === $expected; 
    } 
} 

El problema que estoy teniendo es que cuando mi elector vuelve ACCESS_DENIED, me sale un error 500 cuando el servidor de seguridad redirige al punto de entrada de autenticación:

[2012-10-05 11:09:16] app.ERROR: no x-acme-auth header present in request [] [] 
[2012-10-05 11:09:16] event.DEBUG: Notified event "kernel.exception" to listener "Symfony\Component\Security\Http\Firewall\ExceptionListener::onKernelException". [] [] 
[2012-10-05 11:09:16] security.DEBUG: Access is denied (user is not fully authenticated) by "/home/phil/projects/acme/vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/AccessListener.php" at line 70; redirecting to authentication entry point [] [] 
[2012-10-05 11:09:16] event.DEBUG: Notified event "kernel.exception" to listener "Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelException". [] [] 
[2012-10-05 11:09:16] event.DEBUG: Notified event "kernel.exception" to listener "Symfony\Component\HttpKernel\EventListener\ExceptionListener::onKernelException". [] [] 
[2012-10-05 11:09:16] request.CRITICAL: Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException: Full authentication is required to access this resource. (uncaught exception) at /home/phil/projects/acme/vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php line 109 

Lo que realmente quiero que suceda es solo para devolver una respuesta 403. ¿Es posible hacer esto?

Respuesta

1

Las excepciones capturadas por el núcleo siempre devuelven HTTP 500. (code).

Tendrá que devolver su propia respuesta si la autenticación no es válida.

use Symfony\Component\HttpFoundation\Response; 
$response = new Response(); 

$response->setContent('<html><body><h1>Bad Credentials</h1></body></html>'); 
$response->setStatusCode(403); 
$response->headers->set('Content-Type', 'text/html'); 

// prints the HTTP headers followed by the content 
$response->send(); 
Cuestiones relacionadas