Symfony 3.4如何在登出前判断登录类型并实现SAML重定向?
在Symfony 3.4中处理SAML前置登出重定向
Symfony 3.4没有5.1+那种内置的"登出前"钩子,但我们可以通过事件拦截或者自定义登出控制器来实现需求——先跳去Azure AD完成单点登出,再执行本地登出。下面是具体的实现方案:
核心思路
- 识别当前登出的用户是否是SAML认证的;
- 如果是SAML用户,先重定向到Azure AD的单点登出(SLO)地址,同时指定回调路由;
- 当Azure AD完成登出后,跳回我们的回调路由,在那里执行本地登出操作;
- 非SAML用户直接走默认登出流程。
步骤1:识别SAML用户
你有两种方式判断用户是否通过SAML登录:
- 方式A:通过Token类型判断(推荐,如果你用第三方SAML bundle如OneLogin)
比如OneLogin的SAML bundle会生成SamlToken类型的认证Token,直接判断Token实例即可:$token = $this->get('security.token_storage')->getToken(); if ($token instanceof \OneLogin\SamlBundle\Security\Core\Authentication\Token\SamlToken) { // 是SAML用户 } - 方式B:通过用户实体字段判断
如果你的用户表有auth_type字段(存储local/oauth2/ldap/saml),直接从用户对象获取:$user = $token->getUser(); if ($user instanceof UserInterface && $user->getAuthType() === 'saml') { // 是SAML用户 }
步骤2:拦截登出请求(事件订阅器方案)
用kernel.request事件拦截登出请求,优先级要高于Symfony默认的LogoutListener(优先级64),确保我们的逻辑先执行。
创建事件订阅器
// src/AppBundle/EventSubscriber/SamlLogoutSubscriber.php namespace AppBundle\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\RouterInterface; use OneLogin\Saml2\Auth; class SamlLogoutSubscriber implements EventSubscriberInterface { private $tokenStorage; private $router; private $samlSettings; public function __construct(TokenStorageInterface $tokenStorage, RouterInterface $router, array $samlSettings) { $this->tokenStorage = $tokenStorage; $this->router = $router; $this->samlSettings = $samlSettings; } public static function getSubscribedEvents() { return [ // 优先级高于默认LogoutListener的64 KernelEvents::REQUEST => ['handleSamlLogout', 70], ]; } public function handleSamlLogout(GetResponseEvent $event) { $request = $event->getRequest(); // 只处理登出请求(根据你的security.yml里的logout路径调整) if ($request->getPathInfo() !== '/logout') { return; } $token = $this->tokenStorage->getToken(); // 替换成你的SAML Token判断逻辑 if (!$token instanceof \OneLogin\SamlBundle\Security\Core\Authentication\Token\SamlToken) { return; // 非SAML用户,走默认流程 } // 构造Azure AD的单点登出URL,带上回调地址 $samlAuth = new Auth($this->samlSettings); $returnTo = $this->router->generate('finish_saml_logout', [], RouterInterface::ABSOLUTE_URL); $logoutUrl = $samlAuth->logout($returnTo); // 重定向到Azure AD完成登出 $event->setResponse(new RedirectResponse($logoutUrl)); } }
注册订阅器
在app/config/services.yml里添加:
services: app.saml_logout_subscriber: class: AppBundle\EventSubscriber\SamlLogoutSubscriber arguments: - '@security.token_storage' - '@router' - '%onelogin_saml.settings%' # 你的SAML bundle配置参数 tags: - { name: kernel.event_subscriber }
步骤3:实现回调路由(完成本地登出)
创建一个路由,用来接收Azure AD的登出回调,在这里执行本地登出操作:
添加路由
# app/config/routing.yml finish_saml_logout: path: /finish-saml-logout defaults: { _controller: AppBundle:Security:finishSamlLogout }
编写控制器逻辑
// src/AppBundle/Controller/SecurityController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RedirectResponse; class SecurityController extends Controller { public function finishSamlLogoutAction(Request $request) { // 清除认证Token $this->get('security.token_storage')->setToken(null); // 销毁session $request->getSession()->invalidate(); // 重定向到登录页或首页 return new RedirectResponse($this->generateUrl('homepage')); } }
步骤4:配置Azure AD
记得在Azure AD的SAML应用配置里,把/finish-saml-logout的完整URL添加到允许的回调地址列表中,否则Azure AD不会跳回你的应用。
备选方案:自定义登出控制器
如果你不想用事件订阅器,也可以直接把默认的/logout路由指向自定义控制器,在控制器里做判断:
修改路由
# app/config/routing.yml logout: path: /logout defaults: { _controller: AppBundle:Security:logout }
控制器逻辑
public function logoutAction(Request $request) { $token = $this->get('security.token_storage')->getToken(); if ($token instanceof \OneLogin\SamlBundle\Security\Core\Authentication\Token\SamlToken) { // 跳去Azure AD登出 $samlAuth = new Auth($this->getParameter('onelogin_saml.settings')); $returnTo = $this->generateUrl('finish_saml_logout', [], RouterInterface::ABSOLUTE_URL); return new RedirectResponse($samlAuth->logout($returnTo)); } // 非SAML用户执行默认登出 $this->get('security.token_storage')->setToken(null); $request->getSession()->invalidate(); return new RedirectResponse($this->generateUrl('login')); }
这种方式更直接,但需要自己处理非SAML用户的登出逻辑,适合需求简单的场景。
内容的提问来源于stack exchange,提问作者Kolovos Konstantinos
相关产品推荐
相关产品推荐

