Java Web開(kāi)發(fā)防止多用戶(hù)重復(fù)登錄的完美解決方案
目前web項(xiàng)目中,很多情況都是可以讓同一個(gè)賬戶(hù)信息在不同的登錄入口登錄這次,這樣子就不那么美好了。
推薦閱讀:
Java 多用戶(hù)登錄限制的實(shí)現(xiàn)方法
現(xiàn)在有兩種解決方案:
1、將用戶(hù)的登錄信息用一個(gè)標(biāo)志位的字段保存起來(lái),每次登錄成功就標(biāo)記1,注銷(xiāo)登錄就標(biāo)記為0,當(dāng)標(biāo)記為1的時(shí)候不允許別人登錄。
2、將用戶(hù)的登錄信息保存在application內(nèi)置作用域內(nèi), 然后利用session監(jiān)聽(tīng)器監(jiān)聽(tīng)每一個(gè)登錄用戶(hù)的登錄情況。
很顯然,第一種方式 每次登錄 都需要操作數(shù)據(jù)庫(kù),多了一些不必要的性能開(kāi)銷(xiāo),而且在登錄狀態(tài)下 萬(wàn)一突然電腦關(guān)閉了,那就永遠(yuǎn)都不能登錄了,可用性比較低。
但是第二種方式就不一樣了,可操作性強(qiáng),很方便維護(hù)所有在線(xiàn)用戶(hù)的信息。
接下來(lái) 主要介紹第二種方式的具體實(shí)現(xiàn):
1、在處理登錄的login方法中,先查詢(xún)數(shù)據(jù)庫(kù)驗(yàn)證下該用戶(hù)是否存在,如果存在 判斷該登錄賬戶(hù)是否已經(jīng)鎖定了, 然后從application內(nèi)置作用域?qū)ο笾腥〕鏊械牡卿浶畔?,查看該username賬戶(hù)是否已經(jīng)登錄,如果登錄了,就友好提示下,反之表示可以登錄,將該登錄信息以鍵值對(duì)的方式保存在application中。
代碼如下:
//沒(méi)有使用零配置前 每個(gè)訪問(wèn)的方法都要加上@Action ,否則404
@Action(value="login", results={
@Result(name="index", location="index.jsp"),
})
public String login() throws Exception {
try{
User result = userService.login(user.getFuUserName(), user.getFuPassword());
if(result!=null){
if(result.getFuStatus()!=null && result.getFuStatus()==0){
super.setRequestAttr(Constant.MESSAGE, "抱歉,該用戶(hù)已被鎖定!");
return "error";
}
Map<String, String> loginUserMap = (Map<String, String>) super.getApplicationAttr(Constant.LOGIN_USER_MAP);
boolean isExist = false;
String sessionId = super.getSessionId(false);
if(loginUserMap==null){
loginUserMap = new HashMap<String, String>();
}
for (String username : loginUserMap.keySet()) {
//判斷是否已經(jīng)保存該登錄用戶(hù)的信息 或者 如果是同一個(gè)用戶(hù)進(jìn)行重復(fù)登錄那么允許登錄
if(!username.equals(result.getFuUserName()) || loginUserMap.containsValue(sessionId)){
continue;
}
isExist = true;
break;
}
if(isExist){
super.setRequestAttr(Constant.MESSAGE, "抱歉,該用戶(hù)已登錄!");
return "error";
}else {
loginUserMap.put(result.getFuUserName(), sessionId);
}
//登錄成功
super.setSessionAttr(Constant.LOGIN_USER, result);
super.setApplicationAttr(Constant.LOGIN_USER_MAP, loginUserMap);
logger.info(result.getFuUserName() + " 登錄成功!");
//如果 session中fromUrl有值,就跳轉(zhuǎn)到該頁(yè)面
String fromUrl = (String)super.getSessionAttr(Constant.FROM_URL);
if(fromUrl!=null){
super.setSessionAttr(Constant.FROM_URL, null);
super.getResponse().sendRedirect(fromUrl.toString());
return null;
}
return "index";
}
}
catch (Exception e) {
e.printStackTrace();
logger.info("登錄失敗: "+e.getMessage());
}
super.setRequestAttr("message", "用戶(hù)名或密碼錯(cuò)誤");
return "error";
}
2、登錄入口處理完之后,考慮到會(huì)話(huà)結(jié)束的話(huà),那么對(duì)應(yīng)的登錄用戶(hù)也應(yīng)該相應(yīng)的注銷(xiāo)登錄。我們可以寫(xiě)一個(gè)Session監(jiān)聽(tīng)器,監(jiān)聽(tīng)sessioon銷(xiāo)毀的時(shí)候,我們將登錄的用戶(hù)注銷(xiāo)掉,也就是從application中移除。表示該用戶(hù)已經(jīng)下線(xiàn)了。
代碼如下:
package com.facelook.util;
import java.util.Map;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.apache.log4j.Logger;
import com.facelook.entity.User;
public class SessionListener implements HttpSessionListener{
private Logger logger = Logger.getLogger(this.getClass());
@Override
public void sessionCreated(HttpSessionEvent event) {
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
//在session銷(xiāo)毀的時(shí)候 把loginUserMap中保存的鍵值對(duì)清除
User user = (User)event.getSession().getAttribute("loginUser");
if(user!=null){
Map<String, String> loginUserMap = (Map<String, String>)event.getSession().getServletContext().getAttribute("loginUserMap");
loginUserMap.remove(user.getFuUserName());
event.getSession().getServletContext().setAttribute("loginUserMap",loginUserMap);
}
}
}
web.xml中配置如下:
<!-- session listener --> <listener> <listener-class>com.facelook.util.SessionListener</listener-class> </listener>
3、另外,還有一個(gè)問(wèn)題,如果說(shuō)登錄的用戶(hù)突然關(guān)閉了瀏覽器或者頁(yè)面而沒(méi)有點(diǎn)擊退出按鈕。那么可以利用beforeunload 事件,在瀏覽器刷新或者關(guān)閉的時(shí)候觸發(fā)。
//在刷新或關(guān)閉時(shí)調(diào)用的事件
$(window).bind('beforeunload',function(){
$.ajax({
url:"${ctx}/system/user/user!logout.action",
type:"post",
success:function(){
alert("您已退出登錄");
}
});
);
但是如果一些客觀原因,比如電腦突然關(guān)機(jī),自動(dòng)重啟,等等,這些就沒(méi)法避免了,所以只能等待服務(wù)器端的session會(huì)話(huà)重置之后才可以再登錄。
除非 做一個(gè) 統(tǒng)計(jì)所有在線(xiàn)人員的模塊,管理員在里面進(jìn)行在線(xiàn)人員的登錄登出的狀態(tài)管理,把那些有問(wèn)題的登錄用戶(hù)直接銷(xiāo)毀掉。
接下來(lái)簡(jiǎn)單介紹下在線(xiàn)人員模塊的管理:
1、首先需要一個(gè)session監(jiān)聽(tīng)器來(lái)監(jiān)聽(tīng)所有的回話(huà)create的情況,這時(shí)候每次創(chuàng)建一個(gè)session就可以count+1 ,然后銷(xiāo)毀的時(shí)候count-1 ,另外還需要一個(gè)ServletContext的監(jiān)聽(tīng)器來(lái)監(jiān)聽(tīng)web應(yīng)用的生命周期,獲取servletContext對(duì)象,然后將在線(xiàn)人員總數(shù)統(tǒng)計(jì)出來(lái)存放進(jìn)去;
具體代碼如下:
package com.facelook.util;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.apache.log4j.Logger;
import com.facelook.entity.User;
public class SessionListener implements HttpSessionListener,ServletContextListener{
private int count;
private ServletContext servletContext = null;
public SessionListener() {
count = 0;
}
private Logger logger = Logger.getLogger(this.getClass());
@Override
public void sessionCreated(HttpSessionEvent event) {
count++;
setContext(event);
logger.info("***************the http session is created...***************");
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
//在session銷(xiāo)毀的時(shí)候 把loginUserMap中保存的鍵值對(duì)清除
User user = (User)event.getSession().getAttribute("loginUser");
if(user!=null){
Map<String, String> loginUserMap = (Map<String, String>)event.getSession().getServletContext().getAttribute("loginUserMap");
loginUserMap.remove(user.getFuUserName());
event.getSession().getServletContext().setAttribute("loginUserMap",loginUserMap);
}
count--;
setContext(event);
logger.info("***************the http session is destroyed...***************");
}
public void setContext(HttpSessionEvent httpSessionEvent){
httpSessionEvent.getSession().getServletContext().setAttribute("online", count);
}
@Override
public void contextDestroyed(ServletContextEvent servletcontextevent) {
this.servletContext = null;
logger.info("***************the servlet context is destroyed...***************");
}
@Override
public void contextInitialized(ServletContextEvent servletcontextevent) {
this.servletContext = servletcontextevent.getServletContext();
logger.info("***************the servlet context is initialized...***************");
}
}
2、在UserAction中創(chuàng)建管理在線(xiàn)用戶(hù)的模塊的方法,并且支持強(qiáng)制退出的功能;
/** * 退出登錄
* @return
* @throws ServletException
* @throws IOException
*/
public String logout() throws ServletException, IOException{
try {
Map<String, String> loginUserMap = (Map<String, String>) super.getApplicationAttr(Constant.LOGIN_USER_MAP);
User user = (User) super.getSessionAttr(Constant.LOGIN_USER);
super.removeAttribute(Constant.LOGIN_USER_MAP);
loginUserMap.remove(user.getFuUserName());
super.setApplicationAttr(Constant.LOGIN_USER_MAP,loginUserMap);
logger.info("退出登錄成功!");
} catch (Exception e) {
e.printStackTrace();
logger.error("退出登錄失敗: "+e.getMessage());
}
return INPUT;
}
/**
* 在線(xiàn)用戶(hù)管理
* @return
*/
public String loginManager(){
return SUCCESS;
}
/**
* 強(qiáng)制退出其他用戶(hù)
* @return
*/
public String logoutOther(){
try {
String username = ServletActionContext.getRequest().getParameter("username");
Map<String, String> loginUserMap = (Map<String, String>) super.getApplicationAttr(Constant.LOGIN_USER_MAP);
if(username!=null && loginUserMap.containsKey(username)){
loginUserMap.remove(username);
super.setApplicationAttr(Constant.LOGIN_USER_MAP, loginUserMap);
}
} catch (Exception e) {
e.printStackTrace();
logger.info("強(qiáng)制退出失敗: "+e.getMessage());
}
return null;
}
3、在管理頁(yè)面加載在線(xiàn)用戶(hù)的列表;
對(duì)應(yīng)的方法定義完畢之后,然后再在對(duì)應(yīng)的管理頁(yè)面添加在線(xiàn)列表,具體如下:
<%@page import="java.util.Map"%>
<%@page import="java.util.Map.Entry"%>
<%@ page language="java" pageEncoding="UTF-8" %>
<%@ include file="/common/taglib.jsp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>歡迎來(lái)到Facelook</title>
<%@ include file="/common/resource.jsp" %>
<script type="text/javascript">
<!--
//在刷新或關(guān)閉時(shí)調(diào)用的事件
$(window).bind('beforeunload',function(){
$.ajax({
url:"${ctx}/system/user/user!logout.action",
type:"post",
success:function(){
alert("您已退出登錄");
}
});
});
function logout(username){
if(username=="${sessionScope.loginUser.fuUserName}"){
alert("不允許退出自己賬號(hào)!");
return;
}
$.ajax({
url:"${ctx}/system/user/user!logoutOther.action?username="+username,
type:"post",
success:function(){
$("#tr"+username).hide();
var count = parseInt($("#count").html());
$("#count").html(count-1);
alert("退出成功!");
}
});
}
//-->
</script>
</head>
<body>
<%@ include file="/common/header.jsp" %>
<div id="main" class="wrap">
<%@ include file="/common/lefter.jsp" %>
<div class="righter">
<div class="main">
<h2>登錄列表</h2>
<%
Map<String,String> map = (Map<String,String>)application.getAttribute("loginUserMap");
out.println("目前共有<font id='count'>"+map.size()+"</font>個(gè)用戶(hù)在線(xiàn)??!");
%>
<table border="1" width="400">
<%for(Entry<String,String> m : map.entrySet()){%>
<tr id="tr<%=m.getKey()%>">
<td>
<%=m.getKey()%>
</td>
<td width="80">
<a href="javascript:logout('<%=m.getKey()%>')">強(qiáng)制退出</a>
</td>
</tr>
<%}%>
</table>
</div>
</div>
</div>
<%@ include file="/common/footer.jsp" %>
<%@ include file="/common/message.jsp" %>
</body>
</html>
好了啟動(dòng)部署項(xiàng)目,然后啟動(dòng)服務(wù),進(jìn)入在線(xiàn)用戶(hù)管理模塊,簡(jiǎn)單效果如下圖:

需要注意的是:當(dāng)前登錄用戶(hù) 不允許強(qiáng)制退出自己的登錄信息。
這樣子,基本上可以實(shí)現(xiàn)防止多用戶(hù)登錄的案例了!
以上所述是小編給大家介紹的Java Web開(kāi)發(fā)防止多用戶(hù)重復(fù)登錄的完美解決方案,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- JavaWeb簡(jiǎn)單用戶(hù)登錄注冊(cè)實(shí)例代碼(有驗(yàn)證碼)
- java使用Cookie判斷用戶(hù)登錄情況的方法
- java編程基礎(chǔ)之模仿用戶(hù)登錄代碼分享
- Java Web之限制用戶(hù)多處登錄實(shí)例代碼
- Java+mysql用戶(hù)注冊(cè)登錄功能
- Java傳入用戶(hù)名和密碼并自動(dòng)提交表單實(shí)現(xiàn)登錄到其他系統(tǒng)的實(shí)例代碼
- JavaWeb使用Cookie模擬實(shí)現(xiàn)自動(dòng)登錄功能(不需用戶(hù)名和密碼)
- JavaWeb實(shí)現(xiàn)用戶(hù)登錄注冊(cè)功能實(shí)例代碼(基于Servlet+JSP+JavaBean模式)
- Java CRM系統(tǒng)用戶(hù)登錄功能實(shí)現(xiàn)代碼實(shí)例
相關(guān)文章
Spring Boot集成Quartz注入Spring管理的類(lèi)的方法
本篇文章主要介紹了Spring Boot集成Quartz注入Spring管理的類(lèi)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
java爬蟲(chóng)jsoup解析HTML的工具學(xué)習(xí)
jsoup是一個(gè)解析HTML的第三方j(luò)ava庫(kù),它提供了一套非常方便的API,可使用DOM,CSS以及類(lèi)jQuery的操作方法來(lái)取出和操作數(shù)據(jù),本文就來(lái)開(kāi)始jsoup的使用學(xué)習(xí)2022-07-07
提高開(kāi)發(fā)效率Live?Templates使用技巧詳解
這篇文章主要為大家介紹了提高開(kāi)發(fā)效率Live?Templates使用技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Java 使用Calendar計(jì)算時(shí)間的示例代碼
這篇文章主要介紹了Java 使用Calendar計(jì)算時(shí)間的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
Spring?RestTemplate遠(yuǎn)程調(diào)用過(guò)程
這篇文章主要介紹了Spring?RestTemplate遠(yuǎn)程調(diào)用過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
SpringBoot無(wú)法識(shí)別bootstrap.yml小綠葉問(wèn)題的解決辦法
一般單獨(dú)使用?Spring?Boot?時(shí),bootstrap.yml?文件一般是不會(huì)生效的,也就是沒(méi)有小綠葉圖標(biāo),本文給大家介紹了SpringBoot無(wú)法識(shí)別bootstrap.yml小綠葉問(wèn)題的解決辦法,文中給出了兩種解決方案,需要的朋友可以參考下2024-07-07
Java?HashSet的Removals()方法注意事項(xiàng)
這篇文章主要介紹了Java?HashSet的Removals()方法注意事項(xiàng),文章圍繞制主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-06-06
關(guān)于Java鎖性能提高(鎖升級(jí))機(jī)制的總結(jié)
這篇文章主要介紹了關(guān)于Java鎖性能提高(鎖升級(jí))機(jī)制的總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
Spring的@Validation和javax包下的@Valid區(qū)別以及自定義校驗(yàn)注解
這篇文章主要介紹了Spring的@Validation和javax包下的@Valid區(qū)別以及自定義校驗(yàn)注解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01

