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

Symfony中判断请求是否访问受保护页面及监听逻辑实现

在Symfony中创建监听受保护路由的EventListener

这是一个很实用的需求,我来帮你实现一个干净且通用的解决方案,既能准确识别防火墙后方的受保护路由,又能在session中设置标记:

1. 创建EventListener类

首先,我们创建一个事件订阅者(EventSubscriber),它会监听主请求(master request),并判断当前请求是否指向受保护的路由:

// src/EventListener/ProtectedRouteMarkerListener.php
namespace App\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\HttpFoundation\Session\SessionInterface;

class ProtectedRouteMarkerListener implements EventSubscriberInterface
{
    private $security;
    private $accessDecisionManager;
    private $session;

    public function __construct(
        Security $security,
        AccessDecisionManagerInterface $accessDecisionManager,
        SessionInterface $session
    ) {
        $this->security = $security;
        $this->accessDecisionManager = $accessDecisionManager;
        $this->session = $session;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            // 监听主请求,设置优先级确保在防火墙认证后执行
            KernelEvents::REQUEST => ['onMasterRequest', 10],
        ];
    }

    public function onMasterRequest(RequestEvent $event): void
    {
        // 只处理主请求(排除子请求,比如Twig渲染的嵌入式控制器)
        if (!$event->isMainRequest()) {
            return;
        }

        $request = $event->getRequest();
        $currentToken = $this->security->getToken();

        // 创建匿名Token,用于测试当前路由是否允许匿名访问
        $anonymousToken = new AnonymousToken('app_secret', 'anon.', []);
        
        // 检查匿名用户是否有权限访问当前资源
        $isPublicRoute = $this->accessDecisionManager->decide(
            $anonymousToken,
            ['IS_AUTHENTICATED_ANONYMOUSLY'],
            $request
        );

        // 如果是受保护路由(匿名用户无法访问)
        if (!$isPublicRoute) {
            // 通用标记:记录访问过受保护路由
            $this->session->set('visited_protected_route', true);

            // 可选:针对/admin路由做特殊标记(通过路由名称判断)
            $routeName = $request->get('_route');
            if (str_starts_with($routeName, 'admin_')) {
                $this->session->set('visited_admin_route', true);
            }

            // 可选:仅当用户完全认证时设置标记
            if ($this->security->isGranted('IS_AUTHENTICATED_FULLY')) {
                $this->session->set('authenticated_user_visited_protected', true);
            }
        }
    }
}

2. 服务注册(Symfony 4+可跳过)

如果你使用的是Symfony 4及以上版本,只要你的EventListener在src/EventListener目录下,框架会自动注册这个事件订阅者。如果需要手动配置,可在config/services.yaml中添加:

services:
    App\EventListener\ProtectedRouteMarkerListener:
        arguments:
            - '@security.helper'
            - '@security.access.decision_manager'
            - '@session'
        tags:
            - { name: kernel.event_subscriber }

关键逻辑说明

  • 通用判断受保护路由:通过AccessDecisionManager检查匿名用户是否能访问当前请求,这个方法完全适配你在security.yaml中配置的access_control规则,不需要硬编码路由名称,通用性极强。
  • 主请求过滤:通过$event->isMainRequest()确保我们只处理用户发起的原始请求,排除子请求(比如页面中的嵌入式控制器请求)。
  • 灵活的标记设置:你可以根据需求设置不同的标记:
    • 所有受保护路由访问记录
    • 特定路由(如/admin开头)的访问记录
    • 仅当用户完全认证时的访问记录

补充说明

如果你只想针对特定的防火墙区域判断,也可以通过$request->attributes->get('_firewall_context')获取当前请求对应的防火墙上下文名称,再做针对性处理,这种方式适合多防火墙的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 10:11:13