Symfony2 showing "A token was not found in the SecurityContext" instead of my AuthenticationException's

StackOverflow https://stackoverflow.com/questions/21902109

문제

Hello there

I am trying to set up some sort of WSSE authentication for my api in Symfony2. But when testing unauthorized calls, instead of getting my custom AuthenticationException's, i get an AuthenticationCredentialsNotFoundException with status code 500 from the framework.

Any ideas to why this might happen? Here is my code:

WsseListener.php

<?php
namespace KrugerCorp\VOIPBundle\Security\Firewall;

use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use KrugerCorp\VOIPBundle\Security\Authentication\Token\WsseTenantToken;

class WsseListener implements ListenerInterface
{
    protected $securityContext;
    protected $authenticationManager;
    protected $logger;

    public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger)
    {
        $this->securityContext = $securityContext;
        $this->authenticationManager = $authenticationManager;
        $this->logger = $logger;
    }

    public function handle(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
        if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches))
            return;

        $token = new WsseTenantToken();
        $token->setUser($matches[1]);

        $token->digest  = $matches[2];
        $token->nonce   = $matches[3];
        $token->created = $matches[4];

        try {
            $authToken = $this->authenticationManager->authenticate($token);
            $this->securityContext->setToken($authToken);

            return;
        } catch (AuthenticationException $e) {
            $failedMessage = 'WSSE login failed for '.$token->getUsername()-'. Why? '.$e->getMessage();
            $this->logger->error($failedMessage);

            $response = new Response();
            $response->setStatusCode(403);
            $response->setContent($failedMessage);
            $event->setResponse($response);
            return;
        }

        $response = new Response();
        $response->setStatusCode(403);
        $event->setResponse($response);
    }
} 

WsseProvider.php

<?php

namespace KrugerCorp\VOIPBundle\Security\Authentication\Provider;

use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use KrugerCorp\VOIPBundle\Security\Authentication\Token\WsseTenantToken;

class WsseProvider implements AuthenticationProviderInterface {

    private $tenantProvider;
    private $cacheDir;

    public function __construct(UserProviderInterface $userProvider, $cacheDir)
    {
        $this->tenantProvider = $userProvider;
        $this->cacheDir = $cacheDir;
    }

    public function authenticate(TokenInterface $token)
    {
        $tenant = $this->tenantProvider->loadUserByUsername($token->getUsername());

        if (!$tenant)
            throw new AuthenticationException("Bad credentials.");

        if ($tenant && $this->validateDigest($token->digest, $token->nonce, $token->created, $tenant->getPassword()))
        {
            $authenticatedToken = new WsseTenantToken($tenant->getRoles());
            $authenticatedToken->setUser($tenant);

            return $authenticatedToken;
        }

        throw new AuthenticationException('The WSSE authentication failed.');
    }

    protected function validateDigest($digest, $nonce, $created, $secret)
    {
        if (strtotime($created) > time())
            throw new AuthenticationException('The provided WSSE timestamp is in the future. Nice try.');

        if (time() - strtotime($created) > 300)
            throw new AuthenticationException('The timestamp is outdated.');

        if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time())
            throw new NonceExpiredException('Previously used nonce detected');

        if (!is_dir($this->cacheDir))
            mkdir($this->cacheDir, 0777, true);

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

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

        if ($digest !== $expected)
            throw new AuthenticationException('Bad credentials. Digest is not as expected.');

        return true;
    }

    public function supports(TokenInterface $token)
    {
        return $token instanceof WsseTenantToken;
    }
}     

WsseFactory.php

<?php

namespace KrugerCorp\VOIPBundle\DependencyInjection\Security\Factory;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;

class WsseFactory implements SecurityFactoryInterface
{
    public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
    {
        $providerId = 'security.authentication.provider.wsse.'.$id;
        $container
            ->setDefinition($providerId, new DefinitionDecorator('wsse.security.authentication.provider'))
            ->replaceArgument(0, new Reference($userProvider));

        $listenerId = 'security.authentication.listener.wsse.'.$id;
        $listener = $container->setDefinition($listenerId, new DefinitionDecorator('wsse.security.authentication.listener'));

        return array($providerId, $listenerId, $defaultEntryPoint);
    }

    public function getPosition()
    {
        return 'pre_auth';
    }

    public function getKey()
    {
        return 'wsse';
    }

    public function addConfiguration(NodeDefinition $node)
    {
    }
} 

My firewall

    wsse_secured:
        pattern:    ^/api/.*
        stateless:  true
        wsse:       true
        anonymous: false

My services

wsse.security.authentication.provider:
    class: KrugerCorp\VOIPBundle\Security\Authentication\Provider\WsseProvider
    arguments: ["", "%kernel.cache_dir%/security/nonces"]

wsse.security.authentication.listener:
    class: KrugerCorp\VOIPBundle\Security\Firewall\WsseListener
    arguments: ["@security.context", "@security.authentication.manager", "@logger"]
    tags:
      - { name: monolog.logger, channel: wsse }

And mu bundle class

<?php

namespace KrugerCorp\VOIPBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use KrugerCorp\VOIPBundle\DependencyInjection\Security\Factory\WsseFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class KrugerCorpVOIPBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $extension = $container->getExtension('security');
        $extension->addSecurityListenerFactory(new WsseFactory());
    }
}
도움이 되었습니까?

해결책

try {
    $authToken = $this->authenticationManager->authenticate($token);
    $this->securityContext->setToken($authToken);

    return;
} catch (AuthenticationException $e) {
    // ...
}

You are catching AuthenticationException only!

But

$this->authenticationManager->authenticate($token);

also throws NonceExpiredException that will not be catched.

And my code review... Read the comments.

// I guess loadUserByUsername throws UsernameNotFoundException.
// Wrap it in try catch and throw new AuthenticationException("Bad credentials.");
$tenant = $this->tenantProvider->loadUserByUsername($token->getUsername());

// You will not need this...
if (!$tenant)
    throw new AuthenticationException("Bad credentials.");

// $tenant always true here.
if ($tenant && $this->validateDigest($token->digest, $token->nonce, $token->created, $tenant->getPassword()))
{
    $authenticatedToken = new WsseTenantToken($tenant->getRoles());
    $authenticatedToken->setUser($tenant);

    return $authenticatedToken;
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top