使用Pac4j+Keycloak登出后@SessionScoped Bean未销毁的求助
我在使用Pac4j(版本5.7.7)整合Keycloak时遇到会话销毁异常:已经在shiro.ini中配置了LogoutFilter,开启了localLogout、centralLogout和destroySession参数,登出逻辑是重定向到/oauth/logout,登出后能正常跳转到Keycloak的登出页面并完成中心登出,但**@SessionScoped Bean并未被销毁**——比如RegistrationController的@PostConstruct方法在重新登录时没有触发,自定义的SessionListener的sessionDestroyed方法也从未被调用。现在需要解决如何彻底销毁所有@SessionScoped Bean的问题。
可能的原因与解决办法
1. Shiro会话与Servlet容器会话分离
你当前使用Shiro的DefaultWebSessionManager维护自身会话,而非Servlet容器的HttpSession。Pac4j的LogoutFilter默认仅销毁Pac4j/Shiro层面的会话,不会主动触发Servlet HttpSession的销毁,导致绑定到HttpSession的@SessionScoped Bean无法被回收。
解决办法:
在登出逻辑中主动销毁Servlet HttpSession,修改登出方法:
public void logout() { LOGGER.info("START logout"); try { // 先销毁当前Servlet会话 Faces.getExternalContext().invalidateSession(); // 再重定向到Pac4j登出地址 Faces.redirect(FacesUtil.findApplicationUrl() + "/oauth/logout"); } catch (Exception e) { LOGGER.error(ExceptionUtils.getFullStackTrace(e)); } LOGGER.info("END logout"); }
2. 强制LogoutFilter销毁HttpSession
检查LogoutFilter配置,确保它同时销毁Servlet HttpSession。在shiro.ini中添加:
logoutFilter.invalidateHttpSession = true
(注:部分Pac4j版本中destroySession对应销毁Pac4j会话,invalidateHttpSession才是销毁Servlet HttpSession,需根据版本确认参数名)
3. 验证SessionListener的触发条件
- 确认web.xml中
SessionListener的注册顺序,确保能捕获到会话销毁事件; - 只有当会话主动调用
invalidate()或超时后,容器才会触发sessionDestroyed,需确认登出流程中是否触发了该动作。
4. 清除会话Cookie残留
当前配置的sessionIdCookie.maxAge=36000000,登出时需将其设为0以删除Cookie:
Cookie sidCookie = Faces.getExternalContext().getRequestCookieMap().get("sid"); if (sidCookie != null) { sidCookie.setMaxAge(0); sidCookie.setDomain("localhost"); sidCookie.setPath("/"); Faces.getExternalContext().addResponseCookie(sidCookie.getName(), sidCookie.getValue(), sidCookie.getAttributes()); }
现有配置代码
shiro.ini 配置
#### Session sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie sessionIdCookie.httpOnly = true sessionIdCookie.name = sid sessionIdCookie.domain = localhost sessionIdCookie.maxAge = 36000000 sessionIdCookie.secure = true sessionIdCookie.sameSite = LAX sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager sessionManager.sessionIdCookie = $sessionIdCookie sessionManager.sessionIdCookieEnabled = true securityManager.sessionManager= $sessionManager # 全局会话超时时间:36000000毫秒 = 10小时 sessionManager.globalSessionTimeout= 36000000 logoutFilter = org.pac4j.jee.filter.LogoutFilter logoutFilter.config = $config logoutFilter.localLogout = true logoutFilter.centralLogout = true logoutFilter.destroySession = true ## PAC4J 过滤器映射 /oauth/callback = callbackFilter /oauth/logout = logoutFilter
登出方法
public void logout() { LOGGER.info("START logout"); try { Faces.redirect(FacesUtil.findApplicationUrl() + "/oauth/logout"); } catch (Exception e) { LOGGER.error(ExceptionUtils.getFullStackTrace(e)); } LOGGER.info("END logout"); }
@SessionScoped Bean 示例
@SessionScoped @Named public class RegistrationController implements Serializable { @PostConstruct public void init() { LOGGER.info("START init"); // 初始化逻辑... LOGGER.info("END init"); } }
SessionListener 及配置
public class SessionListener implements HttpSessionListener { private static final Logger LOGGER = LoggerFactory.getLogger(SessionListener.class); @Override public void sessionCreated(HttpSessionEvent se) { LOGGER.info("Session created: ID = {}", se.getSession().getId()); } @Override public void sessionDestroyed(HttpSessionEvent se) { LOGGER.info("Session destroyed: ID = {}", se.getSession().getId()); } }
web.xml 配置
<listener> <listener-class>com.myapp.SessionListener</listener-class> </listener>
依赖版本
<pac4jVersion>5.7.7</pac4jVersion> <bujiVersion>8.1.0</bujiVersion> <jeePac4jVersion>7.1.0</jeePac4jVersion> <dependency> <groupId>io.buji</groupId> <artifactId>buji-pac4j</artifactId> <version>${bujiVersion}</version> </dependency> <dependency> <groupId>org.pac4j</groupId> <artifactId>jakartaee-pac4j</artifactId> <version>${jeePac4jVersion}</version> </dependency> <dependency> <groupId>org.pac4j</groupId> <artifactId>pac4j-core</artifactId> <version>${pac4jVersion}</version> </dependency> <dependency> <groupId>org.pac4j</groupId> <artifactId>pac4j-cas</artifactId> <version>${pac4jVersion}</version> </dependency> <dependency> <groupId>org.pac4j</groupId> <artifactId>pac4j-saml</artifactId> <version>${pac4jVersion}</version> </dependency> <dependency> <groupId>org.pac4j</groupId> <artifactId>pac4j-oidc</artifactId> <version>${pac4jVersion}</version> </dependency> <dependency> <groupId>org.pac4j</groupId> <artifactId>pac4j-http</artifactId> <version>${pac4jVersion}</version> </dependency>
内容的提问来源于stack exchange,提问作者vished2000

