我正在开发一个需要使用相同的用户名和密码阻止多次登录的应用程序。
如果它发生在同一台机器上,那么显然我们需要对用户会话做一些事情,但是如果他们使用相同的用户名和密码在不同的机器上登录,它也应该阻止。
我们必须牢记以下事项:
我将不胜感激。
答案 0 :(得分:25)
如果用户在没有注销的情况下关闭浏览器。
特别是这种情况很难并且检测不可靠。您可以在Javascript中使用beforeunload
事件,但您完全依赖于浏览器是否启用了JS以及特定浏览器是否支持此非标准事件(例如Opera不支持)。这也是我建议只注销以前登录用户而不是阻止登录的主要原因之一。对于用户“忘记”从另一台计算机注销的情况,这也更加用户友好且安全。
最简单的方法是让User
拥有static Map<User, HttpSession>
变量并让它实现HttpSessionBindingListener
(以及Object#equals()
和Object#hashCode()
)。
public class User implements HttpSessionBindingListener {
// All logins.
private static Map<User, HttpSession> logins = new HashMap<User, HttpSession>();
// 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
地图中移除用户。
答案 1 :(得分:5)
在数据库中创建一个表 - 让我们称之为[online_users]
- 有三个字段:
[online_users]
1. username
2. login_time
3. logout_time
每当用户登录时,请将用户名和登录时间插入[online_users]
。
在要求用户登录的所有网页上,请设置此条件:选中[online_users]
以查看用户的logout_time
是否为空。
每当用户按下注销按钮时,请在logout_time
中为该用户的名称设置[online_users]
。
如果有人尝试使用有效的用户名和密码登录,请检查username
和logout_time
并显示一条消息,指出用户已登录。最重要的是,为该用户设置logout_time
到MULTIPLELOGIN
。
如果该用户在任何其他计算机上登录,那么如果他刷新或导航到另一个页面,该网站将告诉他他已经退出。然后,可以将用户重定向到站点的主页。
答案 2 :(得分:3)
在表中添加一个额外字段,其中列名称将“IsLoggedIn”作为位字段并将其设置为true,直到用户登录。一旦用户注销,将其设置为false。 这也需要在会话到期时间内完成。 会话过期后,应使用触发器或SP调用
自动将此字段设置为false仍然欢迎好的解决方案
答案 3 :(得分:2)
我还建议使用Shantanu Gupta的解决方案 - 有一个数据库列指示用户当前已记录,并相应地更新该列。
要“捕获”会话过期,您需要在web.xml
中定义:
<listener>
<listener-class>com.foo.MySessionListener</listener-class>
</listener>
MySessionListener
是HttpSessionListener
接口的实现(由Servlet API提供)。
答案 4 :(得分:2)
我只是建议使用安全框架来处理所有这些细节。例如,Spring Security很容易集成到现有项目中,如果需要可以进行大量定制 - 最重要的是,它具有检测和控制并发登录的内置支持
不需要重新发明轮子,否则你最终会花费大量时间来制造颠簸的轮子。
答案 5 :(得分:2)
也许过于简化,但是嘿......它适用于Web2Py:
只有成功登录后,我才会在auth_membership表中编写SessionID(response.session_id)。 在登录页面(索引页面)上,我检查当前的response.session_id是否等于来自DB的SessionID。 如果是这样 - 一切都很好。 如果不是 - (“较旧的”,第一个)用户被礼貌地注销。
上述工作自从每次登录后都会创建一个新的response.session_id并存储在数据库中。 检查仅在登录页面上进行(在我的应用程序中是最重要的一个,启动许多其他功能),因此上面没有太多的数据库命中。 以上内容不依赖于用户注销。 没有涉及IP地址(其他人提到过,有自己的问题) 它一次只允许一个用户登录,并注销“较旧”的用户。
希望它有所帮助 NeoToren
答案 6 :(得分:0)
我会跟踪每个用户的最后一个已知IP地址以及他们在该IP上的最后时间的时间戳。然后你可以阻止从其他IP访问5分钟,一小时或任何你喜欢的。
每当IP地址切换时,您可以a)使用户的旧会话到期,因此他们被迫重新登录并且b)递增每用户计数器(您可以每小时将其清零)。如果计数器超过5(或其他),您可以阻止对用户帐户的所有访问更长时间。
答案 7 :(得分:0)
您可以在登录时为用户存储某种会话ID。当用户注销或会话过期时,您会再次删除该信息。
当用户尝试登录时,您已经为该用户存储了会话ID,请让用户确认,然后使旧会话无效。
如果浏览器崩溃或类似的情况,用户肯定会立即再次登录,因此让用户等待会话过期可能会很烦人。
这对您的应用程序有意义吗?
答案 8 :(得分:0)
如果您有会话,这可以很容易地执行。对于每个浏览器登录,您应该在会话DB中创建会话记录。会话ID可用作身份验证cookie。会话DB还有一个带用户名的索引。登录时,您可以查询数据库以检查有多少会话。我们实际上允许每种类型的一个会话。例如,用户可以从移动电话登录,也可以从浏览器登录。但它不能有2个浏览器会话。
解决你提到的问题。你有两个选择,
有一个非常短的会话超时(如5分钟)并在每次使用时扩展会话。这样,如果离开而没有注销,用户将自动注销。
颠簸其他会话。新会议将破坏旧会议。碰撞的会话在DB中保留24小时的特殊标志。我们会显示一条消息,告诉用户其他会话正在被碰撞并显示时间和IP。这样,如果用户的帐户遭到入侵,用户就会收到通知。
答案 9 :(得分:0)
我为自己实施了一个可能的解决方案,
在我使用的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 ");
}
}
}
答案 10 :(得分:0)
使用令牌
当用户成功登录时,服务器端将令牌字符串返回到客户端/浏览器端,服务器端保存带有userID-令牌的映射。 客户端反复检查/请求使用该令牌的服务器,如果令牌不相同,则该用户记录乘以倍数。
注销时,它将令牌保存到客户端的cookie或文件系统中,并在下次登录时携带此令牌。
表格:
userid:token:log_date
答案 11 :(得分:0)
如何识别浏览器是否仍处于活动状态?
每分钟进行一次虚拟ajax调用,并在其中记录状态 针对用户的HttpSession,会话ID以及时间 最后一次通话。当同一用户使用新会话登录时,请检查 在HttpSession中针对用户进行检查,并检查时间是否超过了
如果超过一分钟,则表示以前的浏览器已关闭/未激活。
注意:根据您的要求设置时间(在我的情况下为1分钟)。
在进行上述条件检查的同时,添加注释“如果用户关闭浏览器但未注销。”注释中提到的代码。
public class User implements HttpSessionBindingListener