我的应用程序使用
@ViewScoped LoginBean
处理登录,其中注入了存储用户信息和当前 HttpSession 的 @SessionScoped SessionBean
。 该应用程序允许用户N单独的会话。 达到该限制后,用户只能通过杀死最旧的来创建另一个。 这是在同一个 LoginBean
中完成的,方法是向非托管 UserSessionManager
询问最旧的 SessionBean
,然后使其 HttpSession
无效。
因此,使用会话“A”登录,我们使会话“B”无效。 这一切都按计划进行。 但是,在剩余的 JSF 阶段中的某个时候,我们也会丢失会话“A”的
SessionBean
。 追踪 CDI 代码,会话“A”的会话上下文似乎正在被销毁,因此当重新显示完成时,我们拥有所有新的会话 bean。
我们正在使用 MyFaces 2.3.6、OpenWebBeans 2.0.16、OpenJDK 11
这是 OWB 中的错误,还是预期的行为?
我也想知道我是否有根本性的误解。如果我将
SessionBean
保存在我的 UserSessionManager
中并在 different 会话期间检索它,它应该保留其原始状态还是在新的 SessionScoped
上下文中重新评估? 我发现调试很困难,因为我的对象似乎实际上是代理,并且 UI 和调试器有时会显示不同的值。
2020 年 4 月 27 日更新:
@SessionScoped SessionBean
正在被 org.apache.webbeans.web.context.WebContextsService#destroyRequestContext() 销毁,它会销毁“PropaatedSessionContext”。 此 PropaatedSessionContext 由 WebContextsService#destroySessionContext() 设置,它指定要销毁的本地会话,尽管给定了不同的特定会话。 这就是我想知道这是否是 OWB 中的错误的地方。
这是代码的简化示例:
(在此测试代码中,我将 SessionManager 设置为 @ApplicationScoped bean。在原始代码中不是,但行为是相同的。)
@Named("loginbean")
@ViewScoped
public class LoginBean implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
@Inject private ExternalContext externalContext;
@Inject private SessionBean session;
@Inject private SessionManager sessionMgr;
public String killOldestDoLogin() {
List<SessionInfo> sessions = sessionMgr.getSessions();
SessionInfo oldest = sessions.get(0);
sessionMgr.killSession(oldest.getSessionId());
return doLogin();
}
public String doLogin() {
username = username.trim();
if (username != null && username.length() > 0) {
// After a successful login, avoid session fixation attacks by
// rotating the session ID. This isn't strictly necessary as Faces has
// its own session ID that a third party wouldn't have access to
if (externalContext != null) {
HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
if (request != null && request.isRequestedSessionIdValid()) {
newSessionId = request.changeSessionId();
}
}
HttpSession http = (HttpSession)externalContext.getSession(false);
session.setUsername(username);
session.setHttpSession(http);
sessionMgr.addSession(http, session);
}
return "startPage.jsf");
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
.
@Named("sessionbean")
@SessionScoped
public class SessionBean implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private HttpSession httpSession;
public void reset() {
username = null;
httpSession = null;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public HttpSession getHttpSession() {
return httpSession;
}
public void setHttpSession(HttpSession session) {
this.httpSession = session;
}
public String getSessionId() {
return httpSession == null ? "null" : this.httpSession.getId();
}
}
.
@Named("sessionmanager")
@ApplicationScoped
public class SessionManager {
private HashMap<String,HttpSession> sessionMap = new HashMap<>();
private HashMap<String,SessionBean> beanMap = new HashMap<>();
public void addSession(HttpSession http, SessionBean bean) {
beanMap.put(http.getId(), bean);
sessionMap.put(http.getId(), http);
}
public boolean killSession(String sessionId) {
HttpSession session = sessionMap.get(sessionId);
sessionMap.remove(sessionId);
beanMap.remove(sessionId);
if (session != null) {
session.invalidate();
}
return session != null;
}
public List<SessionInfo> getSessions() {
List<SessionInfo> result = new ArrayList<>();
for (String sessionId : sessionMap.keySet()) {
SessionBean bean = beanMap.get(sessionId);
HttpSession http = sessionMap.get(sessionId);
SessionInfo info = new SessionInfo();
info.setUsername(bean.getUsername());
info.setSessionId(sessionId);
info.setHttpSession(http));
result.add(info);
}
return result;
}
}
.
public class SessionInfo {
private String username;
private String sessionId;
private HttpSession httpSession;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public HttpSession getHttpSession() {
return httpSession;
}
public void setHttpSession(HttpSession httpSession) {
this.httpSession = httpSession;
}
}
我对 Apache TomEE 9.1 (MyFaces 3.0.1) 也有同样的问题。为了消除其他会话,我使用了一种似乎有效的解决方法。我没有直接从 JSF servlet 执行 HttpSession.invalidate(),而是启动一个线程,向其中指示要使哪个会话无效,并且用户的 CDI 会话 bean 不会被破坏。