Symfony 6中如何限制某用户提供者访问路由,仅允许另一提供者访问?
解决Symfony中普通用户可访问Admin防火墙路由的问题
你的核心问题是:已登录的普通User实例能绕过admin防火墙的限制访问/admin路径,而你需要基于用户实例类型而非角色来控制权限。以下是直接可行的解决方案:
问题根源
当前的防火墙配置仅区分了登录入口和对应的用户提供者,但没有对已登录用户的实例类型做校验。当普通用户通过main防火墙登录后,访问/admin路径时,admin防火墙的lazy模式会复用已有的认证上下文,而没有验证用户是否是AdminUser实例,导致权限绕过。
方案1:用访问控制规则直接校验用户实例(最简方案)
无需自定义认证器,直接在security.yaml中添加access_control规则,强制校验访问/admin路径的用户类型:
1. 基础配置
在你的安全配置末尾添加:
security: # ... 已有的providers、firewalls配置 ... access_control: # 仅允许AdminUser实例访问/admin下的所有路径 - { path: ^/admin, allow_if: "is_instance_of(user, App\\Entity\\AdminUser)" }
2. 优化登录页访问控制
如果需要允许未登录用户访问/admin/login,同时阻止已登录的普通用户进入该页面,调整规则为:
access_control: # 未登录用户或AdminUser可访问登录页 - { path: ^/admin/login, allow_if: "is_anonymous() or is_instance_of(user, App\\Entity\\AdminUser)" } # 其他/admin路径仅允许AdminUser访问 - { path: ^/admin, allow_if: "is_instance_of(user, App\\Entity\\AdminUser)" }
方案2:自定义认证器(复杂场景适用)
如果需要更复杂的认证逻辑(比如额外的身份校验、日志记录等),可以通过自定义认证器在认证阶段直接校验用户实例类型:
1. 创建AdminAuthenticator类
// src/Security/AdminAuthenticator.php namespace App\Security; use App\Entity\AdminUser; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use Symfony\Component\Security\Http\Authenticator\Passport\Passport; use Symfony\Component\Security\Http\Util\TargetPathTrait; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; class AdminAuthenticator extends AbstractAuthenticator { use TargetPathTrait; public function __construct(private UrlGeneratorInterface $urlGenerator) { } public function supports(Request $request): ?bool { // 仅处理admin登录的POST请求 return $request->getPathInfo() === '/admin/login' && $request->isMethod('POST'); } public function authenticate(Request $request): Passport { $email = $request->request->get('_email', ''); $password = $request->request->get('_password', ''); return new Passport( new UserBadge($email), new PasswordCredentials($password) ); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { // 跳转至预设的目标路径或admin首页 if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) { return new RedirectResponse($targetPath); } return new RedirectResponse($this->urlGenerator->generate('admin_home')); } public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { // 记录错误信息并跳转回登录页 $request->getSession()->set('security.login_error', $exception); return new RedirectResponse($this->urlGenerator->generate('admin_app_login')); } public function checkCredentials($credentials, UserInterface $user): bool { // 强制校验用户是否为AdminUser实例 if (!$user instanceof AdminUser) { throw new CustomUserMessageAuthenticationException('仅管理员账号可登录此后台'); } // 调用默认密码校验逻辑 return password_verify($credentials, $user->getPassword()); } }
2. 在防火墙中配置自定义认证器
修改security.yaml的admin防火墙部分,添加自定义认证器:
admin: lazy: true pattern: ^/admin provider: admin_user_provider custom_authenticators: - App\Security\AdminAuthenticator form_login: login_path: admin_app_login check_path: admin_app_login username_parameter: _email password_parameter: _password default_target_path: admin_home logout: path: admin_app_logout target: admin_app_login
内容的提问来源于stack exchange,提问作者Чарльз Буковски
相关产品推荐
相关产品推荐

