本文先為大家介紹如何利用緩存Cache方便地實現此功能。
Cache與Session這二個狀態對像的其中有一個不同之處,Cache是一個全局對象,作用的范圍是整個應用程序,所有用戶;
而Session是一個用戶會話對象,是局部對象,用於保存單個用戶的信息。
只要把每次用戶登錄后的用戶信息存儲在Cache中,把Cache的Key名設為用戶的登錄名,Cache的過期時間設置為Session的超時時間,在用戶每次登錄的時候去判斷一下Cache[用戶名]是否有值,如果沒有值,證明該用戶沒有登錄,否則該用戶已登錄。
為大家舉一個例子吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
/// <summary>
/// 防止多次登錄
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private
void
Button1_Click(
object
sender, System.EventArgs e)
{
string
strUser =
string
.Empty;
string
strCacheKey =
this
.TextBox1.Text;
strUser = Convert.ToString(Cache[strCacheKey]);
if
(strUser ==
string
.Empty)
{
TimeSpan SessTimeOut =
new
TimeSpan(0, 0, System.Web.HttpContext.Current.Session.Timeout, 0, 0);
Cache.Insert(strCacheKey, strCacheKey,
null
, DateTime.MaxValue, SessTimeOut, CacheItemPriority.NotRemovable,
null
);
Session[
"User"
] = strCacheKey;
this
.Label1.Text = Session[
"User"
].ToString();
}
else
{
this
.Label1.Text =
"這個用戶已經登錄!"
;
}
}
|
在網上又找了下,發現了另外兩種解決方案:
1、通過數據庫狀態位判斷該用戶是否已經登錄。
2、利用session監聽器監聽每一個登錄用戶的登錄情況。
第一種解決方案很簡單,但需要考慮用戶非正常退出的情況,如直接關閉瀏覽器等等,可用性較低。
接下來,主要介紹第二種方案的具體實現:利用session監聽器監聽每一個登錄用戶的登錄情況。
A.用戶登錄后,先去數據庫查詢該登錄名是否存在、是否鎖定,在登錄名存在且非鎖定的情況下,從application內置作用域對象中取出所有的登錄信息,查看該登錄名是否已經登錄,如果登錄了,就友好提示下;反之表示可以登錄,將該登錄信息保存在application中。
主要代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
//
//所有的登錄信息
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()) {
//判斷是否已經保存該登錄用戶的信息,是否為同一個用戶進行重復登錄
if
(!username.equals(user.getFuUserName()) || loginUserMap.containsValue(sessionId)){
continue
;
}
isExist =
true
;
break
;
}
if
(isExist){
//該用戶已登錄
//
}
else
{
//該用戶沒有登錄
loginUserMap.put(result.getFuUserName(), sessionId);
//
}
//
|
B.登錄考慮完之后,來考慮考慮退出。
用戶正常退出時,我們需要將該用戶的登錄信息從session中移除。我們可以寫一個Session監聽器,監聽sessioon銷毀的時候,我們將登錄的用戶注銷掉,也就是從application中移除。表示該用戶已經下線了。
主要代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//
public
void
sessionDestroyed(HttpSessionEvent
event
) {
//
//在session銷毀的時候 把loginUserMap中保存的鍵值對清除
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);
}
//
}
//
|
另外,還有一個問題,如果說登錄的用戶突然關閉了瀏覽器而沒有點擊退出按鈕。那么可以利用beforeunload 事件,在瀏覽器刷新或者關閉的時候觸發。
1
2
3
4
5
6
7
8
9
10
|
//在刷新或關閉時調用的事件
$(window).bind(
'beforeunload'
,function(){
$.ajax({
url:
"${ctx}/system/user/user!logout.action"
,
type:
"post"
,
success:function(){
alert(
"您已退出登錄"
);
}
});
});
|
這樣基本就實現了需求。
大家可以把上面代碼運用到自己的項目中,檢測一下,有效的防止同一賬號的重復登錄,希望大家喜歡這些方法。
ASP.net 實現禁止用戶重復登錄(踢出先登錄的)
應客戶要求,須在系統中實現禁止用戶重復登錄。
我采用的是asp.net自帶的登錄組件,有很多現成屬性和方法可用,比如,Membershipuser.Comment可用來存儲用戶的特定登錄信息,本例中的時間刻錄,就存儲在這個屬性中。注意,存儲后,記得用Membership.UpdateUser 方法來保存信息。
如果沒有采用asp.net自帶的登錄組件,則可通過Application或數據庫來保存,其原理是一樣的。
為了解決用戶提出的“禁止同一用戶名重復登錄”的要求,曾經采用更兩種解決方案。
最初采用的方案,是利用MembershipUser.IsOnline 來判斷用戶是否在線,如果在線,則禁止登錄。
If sUser.IsOnline Then
Me.CustomValidator1.ErrorMessage = "該用戶已經在另一地點登錄!"
args.IsValid = False
Exit Sub
End If
但是,這一方案有一缺點,當用戶非正常退出時,由於Session值的消失要20分鍾左右,因此在這20分鍾內,系統無法判斷客戶端是否已經退出,只能作為用戶仍然在線來對待。也就是說,這20分鍾內,該用戶無法再次登錄,必須等20分鍾后,方可正常登錄。
這樣明顯無法滿足客戶要求。所以,后來我就采用了第二種方案:后登錄的用戶,踢出先登錄的用戶。
第二種方案的實現原理如下:
1.登錄時在User.Comment中保存登錄的時間刻度,同時在Session中保存同一刻度數。代碼如下:
If Membership.ValidateUser(sUserName, sPassword) Then
Dim sLoginTicks As String = Now.Ticks.ToString
sUser.Comment = sLoginTicks
Membership.UpdateUser(sUser) '保存到User.comment值中
Page.Session.Add("LoginTicks", sLoginTicks) '保存到Session中
FormsAuthentication.SetAuthCookie(sUserName, False)
Response.Redirect("Default.aspx")
Else
Response.Write("<script> alert(""用戶名或密碼錯誤!"");location.href = """ & Request.Url.ToString & """;</script>")
Response.End()
End If
2.在頁面登錄驗證時,對比Session中時間刻度,和User.comment中的時間刻度是否相同。后登錄的,這兩個值同時被更新,必定會相同。而先登錄的用戶,由於Session中的值未更新,而User.comment中的值,被后登錄的用戶更新掉了,因此值不同,從而被踢出。代碼如下:
Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
Dim HomePage As String = Me.ResolveUrl("~/Default.aspx")
If Page.Request.IsAuthenticated = True Then '本句用來驗證是否登錄
Dim sUser As MembershipUser = Membership.GetUser
If Page.Session("LoginTicks") IsNot Nothing Then
If Me.Page.Session("LoginTicks").ToString <> sUser.Comment.ToString Then
FormsAuthentication.SignOut()
Response.Write("<script> alert(""相同帳號在其它地點登錄,你被迫注銷。如有疑問,請與網站管理員聯系。"");location.href = """ & HomePage & """;</script>")
Response.End()
End If
End If
End If
End Sub
經測試,該方案完美解決了禁止用戶重復登錄的問題
二、
實現思路:
用戶登錄成功后,將用戶登錄信息存放到Hashtable類型的Application["Online"]里面,其鍵值為SessionID,其Value值為用戶ID;當用戶注銷時,調用Session.Abandon;在Global.asax里面的SessionEnd事件中,將用戶ID從Hashtable中刪除;在用戶訪問頁面時,察看Hashtable中是否有對應的用戶ID如果沒有則判斷用戶不在線(用戶不在線的原因可能是按了注銷按鈕、網頁超時等)
1、公用類中判斷用戶是否在線的函數(供用戶調用)
Code
/**//// <summary>
/// 判斷用戶strUserID是否包含在Hashtable h中
/// </summary>
/// <param name="strUserID"></param>
/// <param name="h"></param>
/// <returns></returns>
public static bool AmIOnline(string strUserID, Hashtable h)
{
if (strUserID == null)
return false;
//繼續判斷是否該用戶已經登陸
if (h == null)
return false;
//判斷哈希表中是否有該用戶
IDictionaryEnumerator e1 = h.GetEnumerator();
bool flag = false;
while (e1.MoveNext())
{
if (e1.Value.ToString().CompareTo(strUserID) == 0)
{
flag = true;
break;
}
}
return flag;
}
2、用戶登錄事件處理:
Code
private void btnlogin_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
//User為自定義的類,其中包含Login方法
User CurUser = new User();
CurUser.UserID = this.username.Text.Trim();
if (MyUtility.AmIOnline(CurUser.UserID, (Hashtable) Application["Online"]))
{
JScript.Alert("您所使用的登錄ID已經在線了!您不能重復登錄!");
return;
}
CurUser.LoginPsw = FormsAuthentication.HashPasswordForStoringInConfigFile(this.password.Text.Trim(), "SHA1");
int ii = CurUser.Login();
StringBuilder sbPmt = new StringBuilder();
switch (ii)
{
case 0: //如果登錄成功,則將UserID加入Application["Online"]中
Hashtable h = (Hashtable) Application["Online"];
if (h == null)
h = new Hashtable();
h[Session.SessionID] = CurUser.UserID;
Application["Online"] = h;
Session["UserID"] = CurUser.UserID;
Session["UserNM"] = CurUser.UserNM;
Session["RoleMap"] = CurUser.RoleMap;
Session["LoginPsw"] = CurUser.LoginPsw;
Session["LoginTime"] = DateTime.Now;
Response.Redirect("ChooseRole.aspx");
break;
case -1:
JScript.Alert("用戶名錯誤!");
break;
case -2:
JScript.Alert("密碼錯誤!");
break;
default:
sbPmt.Append("登錄過程中發生未知錯誤!");
JScript.Alert(sbPmt.ToString());
break;
}
return;
}
3、在Global.asax中的Session_End事件:
Code
protected void Session_End(Object sender, EventArgs e)
{
Hashtable h = (Hashtable) Application["Online"];
if (h[Session.SessionID] != null)
h.Remove(Session.SessionID);
Application["Online"] = h;
}
4、在每一個頁面需要刷新的地方,調用如下代碼:
Code
try
{
if (!common.MyUtility.AmIOnline(Session["UserID"].ToString(), (Hashtable) Application["OnLine"]))
{
//用戶沒有在線 ,轉到登錄界面
Response.Write("<script>parent.document.location.href='Login.aspx';</script>"); ////有框架時用
//Response.Redirect("login.aspx"); ////無框架時用
return;
}
}
catch
{
//會話過期 ,轉到登錄界面
Response.Write("<script>parent.document.location.href='Login.aspx';</script>"); ////有框架時所用
//Response.Redirect("login.aspx"); ////無框架時用
return;
}
深入思考:
由本例的解決方法可以加以延伸,比如,在存儲UserID的時候,將UserID+客戶端IP地址一起存進去,
則在將相應信息取出來分析的時候,可以做到:當用戶在不同的計算機上先后登錄的時候,則允許最近一次的登錄,而將之前的登錄刪除!等等