当他使用相同的凭据登录两次时如何使用户会话无效

Posted

技术标签:

【中文标题】当他使用相同的凭据登录两次时如何使用户会话无效【英文标题】:How to invalidate an user session when he logs twice with the same credentials 【发布时间】:2011-01-23 06:13:14 【问题描述】:

我正在使用带有 Richfaces 和 Facelets 的 JSF 1.2。

我有一个包含许多会话范围 bean 和一些应用程序 bean 的应用程序。

用户使用 Firefox 登录。使用 ID="A" 创建会话; 然后他打开 Chrome 并使用相同的凭据再次登录。使用 ID="B" 创建会话。

创建会话“B”时,我希望能够销毁会话“A”。该怎么做?

还有。当 Firefox 中的用户执行任何操作时,我希望能够显示一个弹出窗口或某种通知,说“您已注销,因为您从其他地方登录”。

我有一个 sessionListener 来跟踪创建和销毁的会话。问题是,我可以将 HTTPSession 对象保存在应用程序范围的 bean 中,并在检测到用户已登录两次时将其销毁。但是有些事情告诉我这是错误的,并且行不通。

JSF 是否在服务器端的某处跟踪会话?如何通过标识符访问它们?如果没有,用户两次登录时如何踢出第一次登录?

【问题讨论】:

【参考方案1】:

独立于 DB 的方法是让User 有一个static Map<User, HttpSession> 变量并实现HttpSessionBindingListener(以及Object#equals()Object#hashCode())。这样,您的 webapp 在发生意外崩溃后仍然可以运行,这可能会导致 DB 值没有得到更新(您当然可以创建一个 ServletContextListener 在 webapp 启动时重置 DB,但这只是越来越多的工作)。

User 应该是这样的:

public class User implements HttpSessionBindingListener 

    // All logins.
    private static Map<User, HttpSession> logins = new ConcurrentHashMap<>();

    // Normal properties.
    private Long id;
    private String username;
    // Etc.. Of course with public getters+setters.

    @Override
    public boolean equals(Object other) 
        return (other instanceof User) && (id != null) ? id.equals(((User) other).id) : (other == this);
    

    @Override
    public int hashCode() 
        return (id != null) ? (this.getClass().hashCode() + id.hashCode()) : super.hashCode();
    

    @Override
    public void valueBound(HttpSessionBindingEvent event) 
        HttpSession session = logins.remove(this);
        if (session != null) 
            session.invalidate();
        
        logins.put(this, event.getSession());
    

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) 
        logins.remove(this);
    


当您如下登录User时:

User user = userDAO.find(username, password);
if (user != null) 
    sessionMap.put("user", user);
 else 
    // Show error.

然后它将调用valueBound(),这将从logins 映射中删除任何以前登录的用户并使会话无效。

当您按如下方式注销User 时:

sessionMap.remove("user");

或者当会话超时时,将调用valueUnbound(),将用户从logins 映射中删除。

【讨论】:

感谢您的回答。我猜是“sessionMap.put("user", user);"应该是“sessionMap.put(用户名,用户);”。否则,如果具有不同凭据的其他用户登录,我们会将第一个用户踢出。 这不正常。您不希望在一个客户端会话期间有不同的登录用户。也不要将会话映射与应用映射混淆。 好的,我现在明白 sessionMap 是 ExternalContext.sessionMap 。它可以工作:)。 valueUnbound()被调用时,你不想invalidate会话吗? @Harry:不。更重要的是,实际上是失效本身导致了方法被调用:)【参考方案2】:
    在数据库userLoggedInCount中创建一个整数字段 在每次登录增量时,都会标记并将结果存储在会话中。 在每个请求中检查数据库中的值和会话中的值,如果会话中的值小于数据库中的值,invalidate() 会话并减少数据库中的值 每当会话被销毁时,该值也会减少。

【讨论】:

我猜我可以使用应用程序范围的类来代替 DB。例如,问题是如何从相位侦听器访问它?不过很好的解决方案。 嗯,可以通过FacesContext获取ExternalContext 这个算法可能不够用。这个理论是完美的,但在实践中,如果浏览器将会话保存在 cookie 中并从中恢复,它就行不通了。看一下步骤 2。如果如我所说,浏览器自动恢复会话,它不会通过任何 LoginHandler 或 LoginMethod 或任何可控的地方来执行该标志的递增。遇到这种情况该怎么办?【参考方案3】:

我喜欢 BalusC 提供的带有 HttpSessionBindingListener 的答案。

但是在Enterprise JavaBeansTM Specification, Version 2.0 中有这样写:

企业 Bean 不得使用读/写静态字段。使用只读静态字段是 允许。因此,建议将企业 bean 类中的所有静态字段 声明为final

那么制作一个ApplicationScoped Bean不是更好吗?它在应用程序范围内存储表而不使用静态字段???

试过了,好像还可以。。。

这是我的例子:

@Named
@ApplicationScoped
public class UserSessionStorage implements java.io.Serializable,HttpSessionBindingListener 

@Inject
UserManagement userManagement;

private static final long serialVersionUID = 1L;

/**
 * Application wide storage of the logins
 */
private final Map<User, List<HttpSession>> logins = new HashMap<User, List<HttpSession>>();

@Override
public void valueBound(final HttpSessionBindingEvent event) 
    System.out.println("valueBound");

    /**
     * Get current user from userManagement...
     */
    User currentUser = userManagement.getCurrentUser();

    List<HttpSession> sessions = logins.get(currentUser);
    if (sessions != null) 
        for (HttpSession httpSession : sessions) 
            httpSession.setAttribute("invalid", "viewExpired");
        
     else 
        sessions = new ArrayList<HttpSession>();
    
    HttpSession currentSession = event.getSession();
    sessions.add(currentSession);
    logins.put(currentUser, sessions);


@Override
public void valueUnbound(final HttpSessionBindingEvent event) 
    System.out.println("valueUnbound");

    User currentUser = userManagement.getCurrentUser();

    List<HttpSession> sessions = logins.get(currentUser);
    if (sessions != null) 
        sessions.remove(event.getSession());
     else 
        sessions = new ArrayList<HttpSession>();
    
    logins.put(currentUser, sessions);

-> 对不起我的英语...

【讨论】:

A HttpSessionBindingListener 不是 EJB。只有那些带有javax.ejb.* 主注释的类,例如@Stateless,才是EJB。

以上是关于当他使用相同的凭据登录两次时如何使用户会话无效的主要内容,如果未能解决你的问题,请参考以下文章

注销时如何使用户会话无效?

如何在某人更改密码后使用户的 JWT 令牌无效

用户注销时会话无效(Spring)

通过 RESTful 调用注销用户(使会话无效)

使 Spring 安全会话无效

当我单击日期两次时,引导日期选择器无效日期问题?