基于IP地址保护API Platform与Symfony构建的API
问题原因与解决方案
核心原因
路径正则匹配错误
你使用的^/*正则无法正确匹配/articles/stocks这类API端点,它仅能匹配空路径、单斜杠或多斜杠开头的路径,而非所有以/开头的子路径,导致IP限制规则未覆盖到实际端点。IP限制逻辑漏洞
原配置中IS_AUTHENTICATED_ANONYMOUSLY是所有未认证用户默认拥有的角色,这条规则仅允许指定IP的用户匿名访问,但未对非指定IP的请求做拒绝处理。如果防火墙开启了anonymous: true,非指定IP的用户仍能绕过限制访问端点。自定义角色未被正确授予
使用ROLE_IP_USER时,你没有通过事件监听器或其他机制给指定IP的请求自动添加该角色,导致is_granted('ROLE_IP_USER')校验无法生效,而防火墙的匿名访问规则优先级更高,使得端点仍处于开放状态。
解决方案
方案1:直接修正access_control规则(快速生效)
通过规则顺序和IP限制,直接拒绝非指定IP的请求:
# config/packages/security.yaml access_control: # 允许首页文档公共访问 - { path: ^/$, roles: PUBLIC_ACCESS } # 允许指定IP的用户匿名访问所有API端点 - { path: ^/.*, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [109.239.112.139, 45.10.152.49] } # 拒绝所有非指定IP的用户访问API端点 - { path: ^/.*, roles: ROLE_NO_ACCESS }
注:ROLE_NO_ACCESS是一个不存在的角色,确保所有用户都不具备,从而触发访问拒绝。
方案2:自定义角色+事件监听器(更灵活)
- 创建IP角色监听器
给指定IP的请求自动添加ROLE_IP_USER角色:
// src/EventListener/IpRoleListener.php namespace App\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\Role; class IpRoleListener implements EventSubscriberInterface { private $tokenStorage; private $allowedIps; public function __construct(TokenStorageInterface $tokenStorage, array $allowedIps) { $this->tokenStorage = $tokenStorage; $this->allowedIps = $allowedIps; } public static function getSubscribedEvents() { return [RequestEvent::class => 'onKernelRequest']; } public function onKernelRequest(RequestEvent $event) { if (!$event->isMainRequest()) return; $clientIp = $event->getRequest()->getClientIp(); if (!in_array($clientIp, $this->allowedIps)) return; $token = $this->tokenStorage->getToken(); if (!$token) return; // 给当前请求的用户添加ROLE_IP_USER角色 $roles = $token->getRoles(); $roles[] = new Role('ROLE_IP_USER'); $newToken = clone $token; $newToken->setRoles($roles); $this->tokenStorage->setToken($newToken); } }
- 配置监听器服务
# config/services.yaml services: App\EventListener\IpRoleListener: arguments: $allowedIps: ['109.239.112.139', '45.10.152.49'] tags: [{ name: kernel.event_subscriber }]
- 更新security配置
# config/packages/security.yaml access_control: - { path: ^/$, roles: PUBLIC_ACCESS } - { path: ^/.*, roles: ROLE_IP_USER }
- 保留ApiResource注解
#[ApiResource( operations: [ new Get(), new GetCollection(), ], order: ['createdAt' => 'DESC'], paginationClientItemsPerPage: true, paginationItemsPerPage: 30, security: 'is_granted(\'ROLE_IP_USER\')' )]
额外注意事项
- 如果应用部署在反向代理(如Nginx、Apache)后,需要在
config/packages/framework.yaml中配置trusted_proxies,确保Symfony能获取真实客户端IP:
# config/packages/framework.yaml framework: trusted_proxies: ['127.0.0.1', '你的代理服务器IP'] trusted_headers: ['x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto']
内容的提问来源于stack exchange,提问作者Cedric Petetin
相关产品推荐
相关产品推荐

