You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony中DataDog/AuditBundle执行顺序问题致无法获取用户Token

解决Symfony中AuditBundle事件订阅者早于TokenAuthenticator执行导致Token为null的问题

这个问题我之前也碰到过,本质是Symfony事件订阅的优先级顺序导致的——AuditBundle的订阅者在TokenAuthenticator完成认证前就触发了,所以TokenStorageInterface里还没有用户Token。下面给你几个可行的解决方案,按改动成本从小到大排序:

1. 调整Audit订阅者的事件优先级

Symfony事件订阅者的默认优先级是0,数值越大越先执行。我们可以把AuditBundle的订阅者优先级设为负数,让它在TokenAuthenticator完成认证后再触发。

情况A:自定义的Audit订阅者

如果是你自己写的订阅者,直接在getSubscribedEvents方法里指定更低的优先级:

use Symfony\Component\HttpKernel\KernelEvents;

class CustomAuditSubscriber implements EventSubscriberInterface
{
    private $securityTokenStorage;

    public function __construct(TokenStorageInterface $securityTokenStorage)
    {
        $this->securityTokenStorage = $securityTokenStorage;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            // 把优先级设为-100,确保在认证逻辑之后执行
            KernelEvents::REQUEST => [['onKernelRequest', -100]],
        ];
    }

    public function onKernelRequest(RequestEvent $event)
    {
        $token = $this->securityTokenStorage->getToken();
        // 这里就能拿到已认证的用户Token了
        if ($token && $token->getUser()) {
            $user = $token->getUser();
            // 执行审计日志逻辑
        }
    }
}

情况B:AuditBundle自带的订阅者

如果是DataDog/AuditBundle自带的订阅者,你可以在services.yaml里重新定义该服务,覆盖它的事件优先级:

services:
    Datadog\AuditBundle\EventSubscriber\AuditSubscriber:
        arguments:
            $securityTokenStorage: '@security.token_storage'
            # 保留Bundle原有的其他参数
            $auditConfig: '@Datadog\AuditBundle\Configuration\AuditConfig'
            $auditLogger: '@Datadog\AuditBundle\Logger\AuditLogger'
        tags:
            # 设置更低的优先级,让它晚于认证逻辑执行
            - { name: kernel.event_subscriber, priority: -100 }

2. 延迟获取Token到认证完成后的事件

如果调整优先级无效,你可以让Audit逻辑监听更晚的事件,比如kernel.controller——这个事件是在路由匹配、认证完成后才触发的,此时Token肯定已经存在。

修改订阅者的事件监听:

public static function getSubscribedEvents(): array
{
    return [
        KernelEvents::CONTROLLER => ['onKernelController'],
    ];
}

public function onKernelController(ControllerEvent $event)
{
    $token = $this->securityTokenStorage->getToken();
    if ($token && $token->getUser()) {
        $user = $token->getUser();
        // 执行审计日志逻辑
    }
}

3. 在TokenAuthenticator认证成功后触发自定义事件

如果你的API认证逻辑是自定义的TokenAuthenticator,可以在认证成功后手动触发一个自定义事件,让Audit订阅者监听这个事件,确保只在认证完成后处理日志。

步骤1:在TokenAuthenticator中触发自定义事件

use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;

class TokenAuthenticator extends AbstractGuardAuthenticator
{
    private $eventDispatcher;

    public function __construct(EventDispatcherInterface $eventDispatcher)
    {
        $this->eventDispatcher = $eventDispatcher;
    }

    public function authenticate(Request $request): PassportInterface
    {
        // 你的Token认证逻辑...
        $user = $this->userProvider->loadUserByIdentifier($username);
        $token = new UsernamePasswordToken($user, null, 'api', $user->getRoles());
        
        // 认证成功后触发自定义事件
        $this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($token), 'api.authentication.success');
        
        return new SelfValidatingPassport(new UserBadge($username));
    }
}

步骤2:让Audit订阅者监听自定义事件

public static function getSubscribedEvents(): array
{
    return [
        'api.authentication.success' => ['onAuthenticationSuccess'],
    ];
}

public function onAuthenticationSuccess(AuthenticationSuccessEvent $event)
{
    $token = $event->getToken();
    $user = $token->getUser();
    // 直接拿到已认证用户,执行审计日志逻辑
}

注意事项

不管用哪种方案,都建议在获取Token后加一层判断,避免未认证请求导致的报错:

$token = $this->securityTokenStorage->getToken();
if (!$token instanceof TokenInterface || !$token->getUser()) {
    // 处理未认证情况,比如跳过日志或记录匿名用户
    return;
}

内容的提问来源于stack exchange,提问作者Vipulw

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:54:25