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

使用Pac4j+Keycloak登出后@SessionScoped Bean未销毁的求助

Pac4j 5.7.7 + 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 06:22:33