Symfony2显示“在SecurityContext中找不到令牌”而不是我的AuthenticationException

时间:2014-02-20 08:28:11

标签: php security symfony firewall

你好

我正在尝试为Symfony2中的api设置某种WSSE身份验证。但是在测试未经授权的调用时,我得到的是来自框架的状态代码为500的AuthenticationCredentialsNotFoundException,而不是获取我的自定义AuthenticationException。

为什么会发生这种情况的任何想法?这是我的代码:

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)
    {
    }
} 

我的防火墙

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

我的服务

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 }

和mu捆绑类

<?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());
    }
}

1 个答案:

答案 0 :(得分:4)

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

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

您正在捕捉 AuthenticationException

但是

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

还会抛出不会被捕获的 NonceExpiredException

我的代码审核......阅读评论。

// 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;
}