Symfony:在防火墙后路由的 SecurityContext 中找不到令牌
Symfony: A Token was not found in the SecurityContext for route behind firewall
在我的 Symfony2 应用程序中,我构建了一个异常侦听器,它让我知道未处理的错误。
当机器人访问我的页面时,我收到有关以下错误的消息,该页面位于防火墙后面:
A Token was not found in the SecurityContext.
我还检索了以下数据:
User agent Mozilla/5.0 (compatible; AhrefsBot/5.0; +http://ahrefs.com/robot/)
Trace as string #0 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2951): Symfony\Component\Security\Http\Firewall\AccessListener->handle(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#1 [internal function]: Symfony\Component\Security\Http\Firewall->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher))
#2 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2205): call_user_func(Array, Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher))
#3 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2138): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#4 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2299): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#5 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(3017): Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#6 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(2990): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1)
#7 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(3139): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#8 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(2383): Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#9 /home/foodmeup.net/production/releases/20150527141710/web/app.php(28): Symfony\Component\HttpKernel\Kernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#10 {main}
自从我上次更新以来,我的所有网站页面都会出现这种情况,但我无法弄清楚问题出在哪里。
如果我自己访问页面,没有问题,没有抛出异常。
我对我设置的防火墙的理解是,如果有人试图访问受保护的资源,他将被重定向到登录页面,而不会抛出任何错误。在这里,我担心某些用户可能会登陆错误页面而不是被重定向到登录页面。当我想通过从抛出错误时访问 referer 来复制错误时,我被正确重定向了,所以我不明白抛出错误与重定向用户时的情况。
编辑:
我的异常侦听器服务:
exception_listener:
class: %exception_listener.class%
arguments: [@router, @session, @security.token_storage, @email_manager, @doctrine, "@=service('kernel').getEnvironment()", @security.authorization_checker]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: 250 }
我的异常侦听器代码:
<?php
namespace AppBundle\EventListener;
use AppBundle\Application\Core\EmailManager;
use AppBundle\Application\Core\JournalManager;
use AppBundle\Entity\User\User;
use AppBundle\Security\Voter\SubscriptionVoter;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
class ExceptionListener
{
/**
* @var Router
*/
private $router;
/**
* @var Session
*/
private $session;
/**
* @var TokenStorage
*/
private $tokenStorage;
/**
* @var EmailManager
*/
private $emailManager;
/**
* @var null
*/
private $environment;
/**
* @var AuthorizationChecker
*/
private $authorizationChecker;
/**
* @var Registry
*/
private $doctrine;
public function __construct(Router $router, Session $session, TokenStorage $tokenStorage, EmailManager $emailManager, Registry $doctrine ,$environment=null, AuthorizationChecker $authorizationChecker)
{
$this->router = $router;
$this->session = $session;
$this->tokenStorage = $tokenStorage;
$this->emailManager = $emailManager;
$this->environment = $environment;
$this->authorizationChecker = $authorizationChecker;
$this->doctrine = $doctrine;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
try {
/** @var $exception */
$exception = $event->getException();
$request = $event->getRequest();
$referer = $request->headers->get('referer');
$manager = $this->doctrine->getManager('logging');
$journalManager = new JournalManager($manager);
if($exception->getCode() == 403)
{
if ($this->authorizationChecker->isGranted(SubscriptionVoter::HAS_SUBSCRIPTION) && !$this->authorizationChecker->isGranted(SubscriptionVoter::SUBSCRIPTION_VALID))
{
$this->session->getFlashBag()->add('warning',"La page précédente n'est pas accessible avec ce portfolio car le paiement n'est pas à jour. Vous devez actualiser votre paiement.");
$response = new RedirectResponse($this->router->generate('renew_subscription'));
}
else
{
$this->session->getFlashBag()->add('warning',"La page précédente n'est pas accessible avec vos droits d'accès et vous avez été redirigé vers l'accueil du site.");
$response = new RedirectResponse($this->router->generate('home'));
}
$event->setResponse($response);
}
elseif ($exception->getMessage() == "Couldn't connect to host, Elasticsearch down?" || $exception->getCode() == 52)
{
$this->session->getFlashBag()->add('warning', "La service de recherche du site a arrêté de fonctionner. Renouvellez votre dernière action si celle si n'a pas été suivie d'effet d'ici 2 minutes.");
$event->setResponse(new RedirectResponse($request->headers->get('referer') ?: $this->router->generate('home')));
}
elseif (
!($exception->getCode() == 404 && !$referer) &&
!($this->contains($exception->getMessage(), array('object not found', 'A Token was not found in the SecurityContext', 'No route found for')) && !strpos($referer, 'foodmeup')) &&
!in_array($this->environment, array('dev', 'test')) &&
!$journalManager->errorExists($exception, $request->getUri(), 1)
)
{
$user = is_object($this->tokenStorage->getToken()) ? $this->tokenStorage->getToken()->getUser() : null;
$user = $user instanceOf User ? $user : null;
$code = $exception->getCode();
$this->emailManager->sendEmail(
'error@foodmeup.net',
'foodmeup@foodmeup.net',
':Core/Email:error.html.twig',
"Une erreur $code s'est produite sur le site",
array(
'date' => new \DateTime(),
'user' => $user,
'exception' => $exception,
'referer' => $request->headers->get('referer'),
'current' => $request->getUri(),
'user_agent' => $_SERVER['HTTP_USER_AGENT']
)
);
$journalManager->addErrorLog($exception, $request->getUri());
}
} catch (\Exception $e)
{
}
}
private function contains($str, array $arr)
{
foreach($arr as $a) {
if (stripos($str,$a) !== false) return true;
}
return false;
}
}
我的防火墙:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
guest:
pattern: /(public/|$|genemu-captcha-refresh|media/cache/)
anonymous: true
context: main_auth
main:
pattern: ^/
anonymous: false
provider: main
context: main_auth
switch_user: { role: ROLE_ADMIN, parameter: _switch_user_parameter }
form_login:
login_path: fos_user_security_login
check_path: fos_user_security_check
success_handler: authentication_site_handler
logout:
path: fos_user_security_logout
target: /
remember_me:
key: "%secret%"
lifetime: 86400 #en secondes
path: /
domain: ~ # Prend la valeur par défaut du domaine courant depuis $_SERVER
oauth:
remember_me: true
resource_owners:
facebook: "/loginhwi/check-facebook"
github: "/loginhwi/check-github"
google: "/loginhwi/check-google"
twitter: "/loginhwi/check-twitter"
linkedin: "/loginhwi/check-linkedin"
flickr: "/loginhwi/check-flickr"
login_path: fos_user_security_login
check_path: fos_user_security_check
failure_path: fos_user_security_login
success_handler: authentication_site_handler
oauth_user_provider:
service: fosubuser.provider
精度:
- emailManager 只是发送一封带有给定参数的电子邮件。
它工作正常
- journalManager 只是记录错误并帮助我
过滤掉已经记录的错误
不用担心这个case/error。当你自己测试你的案例时,你会像任何其他 "real" 冲浪者一样得到预期的行为(重定向到登录页面)。
然后
User agent Mozilla/5.0 (compatible; AhrefsBot/5.0;
+http://ahrefs.com/robot/)
您可以看到该页面是由 Ahrefs bot 请求的。当您以真正的冲浪者身份重定向到其他页面时,它会使用 "header" 操作。但是机器人不处理 headers。所以实际上这是业余程序员经常犯的错误。他们放了类似
的东西
if($notallowed){
header('Location: /login');
}
//... only logged stuff ...//
然后它适用于 "real" 冲浪者,但机器人可以通过并到达 "logged stuff"。因此,在这种情况下,它要么需要在 header 之后立即执行 "die" 命令(必须永远不是用户的不良风格),要么抛出异常(良好的风格)。
所以结论:你提到你最近在更改代码后得到了异常,但很可能是机器人开始抓取以前没有抓取过的新内容。因此,您很可能应该添加一条规则来跳过该异常,因为它仅适用于机器人。但当然你也应该回顾你最近的变化。您还可以查看此类异常的日志并检查用户代理以确保它仅应用于机器人。
在我的 Symfony2 应用程序中,我构建了一个异常侦听器,它让我知道未处理的错误。
当机器人访问我的页面时,我收到有关以下错误的消息,该页面位于防火墙后面:
A Token was not found in the SecurityContext.
我还检索了以下数据:
User agent Mozilla/5.0 (compatible; AhrefsBot/5.0; +http://ahrefs.com/robot/)
Trace as string #0 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2951): Symfony\Component\Security\Http\Firewall\AccessListener->handle(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#1 [internal function]: Symfony\Component\Security\Http\Firewall->onKernelRequest(Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher))
#2 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2205): call_user_func(Array, Object(Symfony\Component\HttpKernel\Event\GetResponseEvent), 'kernel.request', Object(Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher))
#3 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2138): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#4 /home/foodmeup.net/production/releases/20150527141710/app/cache/prod/classes.php(2299): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#5 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(3017): Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.request', Object(Symfony\Component\HttpKernel\Event\GetResponseEvent))
#6 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(2990): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1)
#7 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(3139): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#8 /home/foodmeup.net/production/releases/20150527141710/app/bootstrap.php.cache(2383): Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#9 /home/foodmeup.net/production/releases/20150527141710/web/app.php(28): Symfony\Component\HttpKernel\Kernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#10 {main}
自从我上次更新以来,我的所有网站页面都会出现这种情况,但我无法弄清楚问题出在哪里。 如果我自己访问页面,没有问题,没有抛出异常。
我对我设置的防火墙的理解是,如果有人试图访问受保护的资源,他将被重定向到登录页面,而不会抛出任何错误。在这里,我担心某些用户可能会登陆错误页面而不是被重定向到登录页面。当我想通过从抛出错误时访问 referer 来复制错误时,我被正确重定向了,所以我不明白抛出错误与重定向用户时的情况。
编辑:
我的异常侦听器服务:
exception_listener:
class: %exception_listener.class%
arguments: [@router, @session, @security.token_storage, @email_manager, @doctrine, "@=service('kernel').getEnvironment()", @security.authorization_checker]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException, priority: 250 }
我的异常侦听器代码:
<?php
namespace AppBundle\EventListener;
use AppBundle\Application\Core\EmailManager;
use AppBundle\Application\Core\JournalManager;
use AppBundle\Entity\User\User;
use AppBundle\Security\Voter\SubscriptionVoter;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
class ExceptionListener
{
/**
* @var Router
*/
private $router;
/**
* @var Session
*/
private $session;
/**
* @var TokenStorage
*/
private $tokenStorage;
/**
* @var EmailManager
*/
private $emailManager;
/**
* @var null
*/
private $environment;
/**
* @var AuthorizationChecker
*/
private $authorizationChecker;
/**
* @var Registry
*/
private $doctrine;
public function __construct(Router $router, Session $session, TokenStorage $tokenStorage, EmailManager $emailManager, Registry $doctrine ,$environment=null, AuthorizationChecker $authorizationChecker)
{
$this->router = $router;
$this->session = $session;
$this->tokenStorage = $tokenStorage;
$this->emailManager = $emailManager;
$this->environment = $environment;
$this->authorizationChecker = $authorizationChecker;
$this->doctrine = $doctrine;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
try {
/** @var $exception */
$exception = $event->getException();
$request = $event->getRequest();
$referer = $request->headers->get('referer');
$manager = $this->doctrine->getManager('logging');
$journalManager = new JournalManager($manager);
if($exception->getCode() == 403)
{
if ($this->authorizationChecker->isGranted(SubscriptionVoter::HAS_SUBSCRIPTION) && !$this->authorizationChecker->isGranted(SubscriptionVoter::SUBSCRIPTION_VALID))
{
$this->session->getFlashBag()->add('warning',"La page précédente n'est pas accessible avec ce portfolio car le paiement n'est pas à jour. Vous devez actualiser votre paiement.");
$response = new RedirectResponse($this->router->generate('renew_subscription'));
}
else
{
$this->session->getFlashBag()->add('warning',"La page précédente n'est pas accessible avec vos droits d'accès et vous avez été redirigé vers l'accueil du site.");
$response = new RedirectResponse($this->router->generate('home'));
}
$event->setResponse($response);
}
elseif ($exception->getMessage() == "Couldn't connect to host, Elasticsearch down?" || $exception->getCode() == 52)
{
$this->session->getFlashBag()->add('warning', "La service de recherche du site a arrêté de fonctionner. Renouvellez votre dernière action si celle si n'a pas été suivie d'effet d'ici 2 minutes.");
$event->setResponse(new RedirectResponse($request->headers->get('referer') ?: $this->router->generate('home')));
}
elseif (
!($exception->getCode() == 404 && !$referer) &&
!($this->contains($exception->getMessage(), array('object not found', 'A Token was not found in the SecurityContext', 'No route found for')) && !strpos($referer, 'foodmeup')) &&
!in_array($this->environment, array('dev', 'test')) &&
!$journalManager->errorExists($exception, $request->getUri(), 1)
)
{
$user = is_object($this->tokenStorage->getToken()) ? $this->tokenStorage->getToken()->getUser() : null;
$user = $user instanceOf User ? $user : null;
$code = $exception->getCode();
$this->emailManager->sendEmail(
'error@foodmeup.net',
'foodmeup@foodmeup.net',
':Core/Email:error.html.twig',
"Une erreur $code s'est produite sur le site",
array(
'date' => new \DateTime(),
'user' => $user,
'exception' => $exception,
'referer' => $request->headers->get('referer'),
'current' => $request->getUri(),
'user_agent' => $_SERVER['HTTP_USER_AGENT']
)
);
$journalManager->addErrorLog($exception, $request->getUri());
}
} catch (\Exception $e)
{
}
}
private function contains($str, array $arr)
{
foreach($arr as $a) {
if (stripos($str,$a) !== false) return true;
}
return false;
}
}
我的防火墙:
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
guest:
pattern: /(public/|$|genemu-captcha-refresh|media/cache/)
anonymous: true
context: main_auth
main:
pattern: ^/
anonymous: false
provider: main
context: main_auth
switch_user: { role: ROLE_ADMIN, parameter: _switch_user_parameter }
form_login:
login_path: fos_user_security_login
check_path: fos_user_security_check
success_handler: authentication_site_handler
logout:
path: fos_user_security_logout
target: /
remember_me:
key: "%secret%"
lifetime: 86400 #en secondes
path: /
domain: ~ # Prend la valeur par défaut du domaine courant depuis $_SERVER
oauth:
remember_me: true
resource_owners:
facebook: "/loginhwi/check-facebook"
github: "/loginhwi/check-github"
google: "/loginhwi/check-google"
twitter: "/loginhwi/check-twitter"
linkedin: "/loginhwi/check-linkedin"
flickr: "/loginhwi/check-flickr"
login_path: fos_user_security_login
check_path: fos_user_security_check
failure_path: fos_user_security_login
success_handler: authentication_site_handler
oauth_user_provider:
service: fosubuser.provider
精度:
- emailManager 只是发送一封带有给定参数的电子邮件。 它工作正常
- journalManager 只是记录错误并帮助我 过滤掉已经记录的错误
不用担心这个case/error。当你自己测试你的案例时,你会像任何其他 "real" 冲浪者一样得到预期的行为(重定向到登录页面)。
然后
User agent Mozilla/5.0 (compatible; AhrefsBot/5.0; +http://ahrefs.com/robot/)
您可以看到该页面是由 Ahrefs bot 请求的。当您以真正的冲浪者身份重定向到其他页面时,它会使用 "header" 操作。但是机器人不处理 headers。所以实际上这是业余程序员经常犯的错误。他们放了类似
的东西if($notallowed){
header('Location: /login');
}
//... only logged stuff ...//
然后它适用于 "real" 冲浪者,但机器人可以通过并到达 "logged stuff"。因此,在这种情况下,它要么需要在 header 之后立即执行 "die" 命令(必须永远不是用户的不良风格),要么抛出异常(良好的风格)。
所以结论:你提到你最近在更改代码后得到了异常,但很可能是机器人开始抓取以前没有抓取过的新内容。因此,您很可能应该添加一条规则来跳过该异常,因为它仅适用于机器人。但当然你也应该回顾你最近的变化。您还可以查看此类异常的日志并检查用户代理以确保它仅应用于机器人。