单点登录的一系列尝试及最终解决
曾经一时兴起,想搞一个实现单点登录的,然后实现起来发现了好多问题,首先原理明白,代码也很好写,如下:
Default.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default"%>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<br />
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<br />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="登陆" />
<asp:Button ID="Button2" runat="server" OnClick="Button2_Click" Text="注销" />
</asp:Content>
Default.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
// 作为唯一标识的str_Key,应该是唯一的,这可根据需要自己设定规则。
// 做为测试,这里用用户名和密码的组合来做标识;也不进行其它的错误检查。
// 生成str_Key
string str_Key = TextBox1.Text + " " + TextBox2.Text;
// 得到Cache中的给定str_Key的值
string str_User = Convert.ToString(Cache[str_Key]);
// Cache中没有该str_Key的项目,表名用户没有登录,或者已经登录超时
if (str_User == String.Empty)
{
// TimeSpan构造函数,用来重载版本的方法,进行判断是否登录。
TimeSpan SessTimeOut = new TimeSpan(0, 0, HttpContext.Current.Session.Timeout, 0, 0);
HttpContext.Current.Cache.Insert(str_Key, str_Key, null, DateTime.MaxValue, SessTimeOut, CacheItemPriority.NotRemovable, null);
Session["User"] = str_Key;
// 首次登录成功
Response.Write("<h2 style='color:red'>你好,登录成功!");
}
else
{
// 在 Cache 中存在该用户的记录,表名已经登录过,禁止再次登录
Response.Write("<h2 style='color:red'>抱歉,您好像已经登录了!");
return;
}
}
protected void Button2_Click(object sender, EventArgs e)
{
Response.Redirect("Default2.aspx");
}
}
实现效果如下:
重复登录
注销之后再登录
但是往往有些用户的习惯是不会去管注销,退出一类的按钮,而是直接选择关闭浏览器,从而就会导致一类问题,我们学校的老的一款选课系统也是这样,不点注销直接关浏览器,下次登录就只能半小时后了,为了解决这个问题曾经查过很多资料,大体思路就是如何在用户关闭浏览器的时候直接清除掉session,从而是cache失去依赖项而被移除,思路上是正确的,但是随之而来的一个巨大难点是如何判断用户关闭了浏览器。从而进行了很多尝试,如下:
- dom里面有一个属性closed,当它为true时即浏览器窗口window关闭,但用过之后发现,这样是没办法区分刷新和关闭的,因为刷新是先close,再open,从而宣告这个方法失败。
-
用js判断,代码如下:
<!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>
<title>Js智能判断浏览器是关闭还是刷新</title>
<meta http-equiv="content-type" content="text/html;charset=gb2312">
</head>
<body>
关闭或刷新浏览器试试!
<script language="javascript">
window.onbeforeunload=function(){
var n=window.event.screenX-window.screenLeft;
var b=n>document.documentElement.scrollWidth-20;
if(b&&window.event.clientY<0||window.event.altKey){
alert("关闭");
}else{
alert("刷新");
}
}
</script>
</body>
</html>
最终测试结果失败的彻彻底底,刷新事件部分浏览器能判断出来,但是关闭事件所有浏览器都无法判断
- 用ajax延迟判断,如一所说,dom有一个关闭属性,关闭是直接close为true,刷新则是先close,再open,我可以在页面加载的时候就open一个高和宽都为0的不可见的窗口,里面用timer将事件的判断延迟处理一下,一般来说刷新的close和open是连在一起的,所以只需要延迟几毫秒,判断有没有调用open就可以了。最后测试方法可行,但是这样会非常耗资源,给用户和服务器带来不必要的压力,不推荐使用。
- 心跳包算法,即页面打开后隔一段时间给服务器发一个空包,让服务器知道这个页面还活着,当用户关闭浏览器,即停止发包,心跳停止,服务器认为页面已死,清除掉session。这个得涉及到比较高端的用法,新手不建议用,很容易因为发包不当而导致ddos攻击。
- 结合cookie和session,cookie不设置过期时间,即为浏览器的生命周期,关闭浏览器就会清除,结合它二者判断。这是我目前找到的最有用的方法,但由于cookie是保存在用户端的,会有一些安全隐患。
昨晚趁学长在问了一下学长,才发现自己的思维一直进入了一个误区,其实没必要用户关闭浏览器就清除session,当用户关闭浏览器,然后在session过期前再次打开页面的时候,直接让用户跳过登陆,仔细想想新浪和腾讯可不就是这么干的么,最后的实现代码如下:
Default.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
string str_User = Convert.ToString(Cache[Convert.ToString(Session["User"])]);
// Cache中没有该str_Key的项目,表名用户没有登录,或者已经登录超时
if (str_User != String.Empty)
{
Response.Write("<h2 style='color:red'>你好,登录成功!");
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
// 作为唯一标识的str_Key,应该是唯一的,这可根据需要自己设定规则。
// 做为测试,这里用用户名和密码的组合来做标识;也不进行其它的错误检查。
// 生成str_Key
string str_Key = TextBox1.Text + " " + TextBox2.Text;
// 得到Cache中的给定str_Key的值
string str_User = Convert.ToString(Cache[str_Key]);
// Cache中没有该str_Key的项目,表名用户没有登录,或者已经登录超时
if (str_User == String.Empty)
{
// TimeSpan构造函数,用来重载版本的方法,进行判断是否登录。
TimeSpan SessTimeOut = new TimeSpan(0, 0, HttpContext.Current.Session.Timeout, 0, 0);
HttpContext.Current.Cache.Insert(str_Key, str_Key, null, DateTime.MaxValue, SessTimeOut, CacheItemPriority.NotRemovable, null);
Session["User"] = str_Key;
// 首次登录成功
Response.Write("<h2 style='color:red'>你好,登录成功!");
}
else
{
// 在 Cache 中存在该用户的记录,表名已经登录过,禁止再次登录
Response.Write("<h2 style='color:red'>抱歉,您好像已经登录了!");
return;
}
}
protected void Button2_Click(object sender, EventArgs e)
{
Response.Redirect("Default2.aspx");
}
}
最后实现的效果是登陆之后不注销,关闭窗口,然后再打开,就会自动登录了,截图
至此单点登录问题暂时告一段落