使用相同的用户名和密码防止多次登录

我正在开发一个应用程序,需要使用相同的用户名和密码来防止多次登录。

如果它发生在同一台机器上,那么显然我们需要对用户会话做一些事情,但是如果它们使用相同的用户名和密码登录到不同的机器上,它也应该防止。

我们必须牢记以下事项:

  1. 如果用户关闭浏览器而不注销。
  2. 如果会话超时。

我将不胜感激任何帮助。

如果用户关闭浏览器而不注销。

特别是这种情况很难并且检测不可靠。 您可以在Javascript中使用beforeunload事件,但是您完全依赖于浏览器是否启用了JS以及特定浏览器是否支持此非标准事件(例如Opera不支持)。 这也是我建议只注销以前登录用户而不是阻止登录的主要原因之一。 对于用户“忘记”从另一台计算机注销的情况,这也更加用户友好且安全。

最简单的方法是让User拥有一个static Map变量,并让它实现HttpSessionBindingListener (和Object#equals()Object#hashCode() )。

 public class User implements HttpSessionBindingListener { // All logins. private static Map logins = new HashMap(); // 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) { request.getSession.setAttribute("user", user); } else { // Show error. } 

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

当您注销User ,如下所示:

 request.getSession().removeAttribute("user"); 

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

在数据库中创建一个表 – 让我们称之为[online_users] – 有三个字段:

 [online_users] 1. username 2. login_time 3. logout_time 

每当用户登录时,将用户名和登录时间插入[online_users]

在要求用户登录的所有页面上,放置此条件:选中[online_users]以查看用户的logout_time是否为空。

每当用户按下注销按钮时,在[online_users]中为该用户的名称设置logout_time

如果有人尝试使用活动用户名和密码登录,请检查usernamelogout_time并显示一条消息,指出用户已经登录。 最重要的是,将logout_time设置logout_time用户的MULTIPLELOGIN

如果该用户登录到任何其他计算机上,那么如果他刷新或导航到另一个页面,该站点将告诉他他已经注销。 然后,可以将用户重定向到站点的主页。

在表中添加一个额外字段,列名称将“IsLoggedIn”作为位字段,并将其设置为true,直到用户登录。一旦用户注销,将其设置为false。 这也需要在会话到期时间内完成。 会话到期后,应使用触发器或SP调用自动将此字段设置为false

仍然欢迎好的解决方案

我还建议使用Shantanu Gupta的解决方案 – 有一个数据库列指示用户当前已记录,并相应地更新该列。

为了“捕获”会话过期,您需要在web.xml定义:

  com.foo.MySessionListener  

其中MySessionListenerHttpSessionListener接口的实现(由Servlet API提供)。

我只是建议使用安全框架来处理所有这些细节。 例如, Spring Security很容易集成到现有项目中,如果需要可以进行大量定制 – 最重要的是, 它内置了对检测和控制并发登录的支持

不需要时不要重新发明轮子,否则你最终会花费大量时间来制造颠簸的轮子。

也许过于简化了,但是嘿……它适用于Web2Py:

只有在成功登录时,我才会在auth_membership表中编写SessionID(response.session_id)。 在登录页面(索引页面)上,我检查当前的response.session_id是否等于来自DB的SessionID。 如果是这样 – 一切都很好。 如果不是 – (“较旧的”,第一个)用户被礼貌地注销。

以上工作因为每次登录都会创建一个新的response.session_id并存储在数据库中。 检查仅在登录页面上进行(在我的应用程序中是最重要的一个,启动许多其他function),因此上面没有太多的数据库命中。 以上内容不依赖于用户注销。 没有涉及IP地址(其他人提到过,遇到了自己的问题)它只允许一个用户一次登录,并且它会注销“较旧”的用户。

希望它能帮助NeoToren

您可以在登录时为用户存储某种会话ID。当用户注销或会话过期时,您将再次删除该信息。

当用户尝试登录,并且您已经为该用户存储了会话ID时,让用户确认该会话ID,然后使旧会话无效。

如果浏览器崩溃或类似的情况,用户肯定会想立即再次登录,因此让用户等待会话过期可能会很烦人。

这对您的应用程序有意义吗?

我会跟踪每个用户的最后一个已知IP地址以及它们在该IP上的最后时间的时间戳。 然后你可以阻止从其他IP访问5分钟,一小时或任何你喜欢的。

每当IP地址切换时,您可以a)使用户的旧会话到期,因此他们被迫重新登录并且b)递增每用户计数器(您可以每小时将其清零)。 如果计数器超过5(或其他),您可以阻止对用户帐户的所有访问更长时间。

如果您有会话,这可以很容易地执行。 对于每个浏览器登录,您应该在会话DB中创建会话记录。 会话ID可用作身份validationcookie。 会话DB还有一个带用户名的索引。 登录时,您可以查询数据库以检查有多少会话。 我们实际上允许每种类型的一个会话。 例如,用户可以从移动电话登录,也可以从浏览器登录。 但它不能有2个浏览器会话。

解决你提到的问题。 你有2个选择,

  1. 有一个非常短的会话超时(如5分钟)并在每次使用时扩展会话。 这样,如果离开而没有注销,用户将自动注销。

  2. 碰到其他会话。 新会议颠覆了旧会议。 碰撞的会话在DB中保留24小时的特殊标志。 我们会显示一条消息,告诉用户其他会话正在被碰撞并显示时间和IP。 这样,如果用户的帐户遭到入侵,用户将收到通知。

我为自己实现了一个可能的解决方案,

在我使用的loginFilter中,我在我的系统上的用户记录中设置了lastloggedin,userloggedin和userSession。

  user.setUser_lastlogged(new Date()); user.setUser_loggedin(true); user.setSessionId(request.getSession().getId()); appService.saveUsers(user); 

所以当我去任何我的struts2动作时,我在prepare方法中有一段代码。

 @Override public void prepare() throws Exception { UsersBase usercheck = appservice.getUserByUsername((String)request.getSession().getAttribute("j_username")); if(request.getSession().getId().equals(usercheck.getSessionId())){ request.getSession().invalidate(); } } 

这将在用户登录另一台计算机时将其记录下来,或者如果您不想将其登录,我可以在loginFilter上执行以下操作

  UsersBase userdto = appService.getUserByUsername(username); if (userdto != null) { if ((userdto.getUser_loggedin())) { if (request.getSession().getId().equals(userdto.getSessionId())) { authRequest.eraseCredentials(); request.getSession().setAttribute("error", "You are already logged in "); } } } 

使用令牌

当用户登录成功时,服务器端将令牌字符串返回给客户端/浏览器端,服务器端使用userID – token保存映射。 客户端使用该令牌反复检查/请求服务器,如果令牌不相同,则该用户记录乘法次数。

注销时,它会将令牌保存到客户端的cookie或文件系统中,并在下次登录时带来此令牌。

表:

 userid:token:log_date