一  数据库

  除了用户表之外,新建一个外联表<用户票据表> fdUsTiUserID,fdUsTiType,fdUsTiTicket 分别对应用户ID,客户端类型(PC,mobile) 票据值,每次用户登录时将票据信息更新到数据库

二 思路

  1.获取用户:

   通过检查当前请求的Sessions.UserId或者Cookies.UserId是否存在;Sessions.UserId存在时直接从缓存获取用户数据,缓存不存在则从数据库中直接获取;否则通过Cookies.UserId和票据从数据库中读取用户信息;(票据也是存在客户端 Cookies.UserTicket)

  2.判断用户合法

  通过第一步的获取用户信息之后判断用户是否为null,如果为null直接返回,如果不为null则判断缓存票据和当前请求票据(Cookies.UserTicket)是否一致,如果一致则返回当前用户,不一致则判断为已经异地登录 当前票据失效,返回null;(如果请求合法 返回之前判断Session.UserId是否存在,不存在的话重建SessionId)

三 关于多客户端缓存用户等的共享

同一用户多客户端登录 会在多客户端存储用户的Session.UserId和针对各个客户端的不同票据值 然后只缓存一份用户信息 通过每次请求时的票据合法性来判断是否登录

  下面是以上的代码

public static GP_Users_Model user
        {
            get
            {
                GP_Users_Model _user = null;
                )
                {
                    //从缓存中获取用户,如果为null直接从数据库中根据ID读取(可能缓存过期,则直接从数据库中读取)
                    _user = GP_Users_Bll.Instance.GetLoginUser((int)Sessions.UserID);
                }
                 && Cookies.UserTicket != "")
                {
                    //根据用户ID和登录票据从数据库中读取用户
                    _user = GP_Users_Bll.Instance.GetLoginUser((int)Cookies.UserID, Cookies.UserTicket);
                }
                if (_user == null) return null;  //用户不存在

                //检查用户当前请求票据是否合法
                if (!loginCacheTicket.Test(_user.fdUserID.Value, Cookies.UserTicket))
                {
                    //重置该用户票据缓存,将用户的所有客户端票据缓存到Rediss
                    GP_Users_Bll.Instance.ResetUserTicket(_user.fdUserID.Value);
                }
                //重置该用户票据缓存后再次检查
                if (!loginCacheTicket.Test(_user.fdUserID.Value, Cookies.UserTicket))
                    return null;//异地登录

                //如果Session过期 则重建Session
                 )
                    Sessions.UserID = _user.fdUserID;
                return _user;
            }
        }

Sessions类

  说明:所有用户都用的同一个SessionNames.User_Id不会导致用户的缓存SessionId被覆盖,在底层有处理 具体的下面会有描述 关于Redis实现Session

public class SessionNames
{
    public const string User_Id    ="USERID";
}

public static class Sessions
{
    public static int? UserID
    {
        get
        {
            try { return (int)GetSession(SessionNames.User_Id); }
                catch { return null; }
        }
        set
        {
            SetSession(SessionNames.User_Id, value);
        }
    }
    public static object GetSession(string name)
    {
        return GPRedisSessions.Instance.GetSession(name);
        //if (HttpContext.Current.Session[name] == null) return null;
        //return HttpContext.Current.Session[name];
    }
    public static object SetSession(string name, object value)
    {
        return GPRedisSessions.Instance.SetSession(name, value);
        //HttpContext.Current.Session[name] = value;
        //return value;
    }
}

Cookies类
public class CookieNames
{
    public const string User_Id    ="UID";
    public const string User_Ticket="USERTICKET";
}

public static class Cookies
{
    public static string UserTicket
    {
        get
        {
            try { return GetCookie(CookieNames.User_Ticket); }
            catch { return null; }
        }
        set { SetCookie(CookieNames.User_Ticket, value); }
    }

    public static string UserID
    {
        get
        {
            try { return GetCookie(CookieNames.User_Id); }
            catch { return null; }
        }
        set { SetCookie(CookieNames.User_Id, value); }
    }

    public static string GetCookie(string name)
    {
        if (HttpContext.Current.Request.Cookies[name] == null || HttpContext.Current.Request.Cookies[name].Value == null) return "";
        return UrlEncoder.UnEscape(HttpContext.Current.Request.Cookies[name].Value);
    }
    /// <summary>
    ///
    /// </summary>
    /// <param name="name"></param>
    /// <param name="value"></param>
    /// <param name="expires">超时时间,不设置则为浏览器生命周期</param>
    /// <returns></returns>
    public static string SetCookie(string name, string value)
    {
        SetCookie(name, value, null);
        return value;
    }
    public static string SetCookie(string name, string value, DateTime? expires)
    {
        return SetCookie(name, value, expires, true);
    }
    /// <summary>
    ///
    /// </summary>
    /// <param name="name"></param>
    /// <param name="value"></param>
    /// <param name="expires"></param>
    /// <param name="httpOnly">是否仅服务器端可读</param>
    /// <returns></returns>
    public static string SetCookie(string name, string value, DateTime? expires, bool httpOnly)
    {
        if (HttpContext.Current.Request.Cookies[name] == null)
        {
            HttpCookie co = new HttpCookie(name);
            co.HttpOnly = httpOnly;
            HttpContext.Current.Request.Cookies.Add(co);
        }
        HttpContext.Current.Request.Cookies[name].Value = UrlEncoder.Escape(value);
        if (expires != null) HttpContext.Current.Request.Cookies[name].Expires = (DateTime)expires;

        if (HttpContext.Current.Response.Cookies[name] == null)
        {
            HttpCookie co = new HttpCookie(name);
            co.HttpOnly = httpOnly;
            HttpContext.Current.Response.Cookies.Add(co);
        }
        HttpContext.Current.Response.Cookies[name].Value = UrlEncoder.Escape(value);
        if (expires != null) HttpContext.Current.Response.Cookies[name].Expires = (DateTime)expires;
        return value;
    }
}

类 GPRedisSessions Redis存储Session

  说明:主要是根据 GPRedisSessionID.Instance.Get()返回的值来确定Session值不会被覆盖, 所有存储到Redis的Session都会在客户端存一个access值     

internal class GPRedisSessions
    {
        private static string GetKey(string sessionName)
        {
            return "MYWEB." + sessionName + "." + GPRedisSessionID.Instance.Get();
        }

        private static GPRedisSessions instance = new GPRedisSessions();
        public static GPRedisSessions Instance
        {
            get { return instance; }
        }
        private GPRedisSessions() { }

        public void Clear()
        {
            using (RedisClient RdsClient = RedisClientHelper.RdsClient)
            {
                foreach (string name in SessionNames.GetAllNames()) RdsClient.Remove(GetKey(name));
            }
        }

        public void Remove(string name)
        {
            using (RedisClient RdsClient = RedisClientHelper.RdsClient)
            {
                RdsClient.Remove(GetKey(name));
            }
        }

        public object GetSession(string name)
        {
            using (RedisClient RdsClient = RedisClientHelper.RdsClient)
            {
                byte[] v = RdsClient.Get(GetKey(name));
                ) return null;
                return RedisClientHelper.BytesToObject(v);
            }
        }

        public object SetSession(string name, object value)
        {
            using (RedisClient RdsClient = RedisClientHelper.RdsClient)
            {
                if (value == null) { Remove(name); return null; }
                RdsClient.Set(GetKey(name), RedisClientHelper.ObjectToBytes(value), DateTime.Now.AddDays());
                return value;
            }
        }
    }

internal class GPRedisSessionID : ISessionID
    {
        private static GPRedisSessionID instance = new GPRedisSessionID();
        public static GPRedisSessionID Instance
        {
            get { return instance; }
        }
        private GPRedisSessionID() { }

        private static string New_Redis_Session_ID
        {
            get
            {
                , ) + RequestHelper.GetClientIP() + DateTime.Now.ToString());
            }
        }

        public string Get()
        {
            using (RedisClient RdsClient = RedisClientHelper.RdsClient)
            {
                string ky = string.Empty;
                if (Cookies.ContainCookie(CookieNames.Open_Access_ID))
                {
                    ky = RedisKeysConfig.Key_CookieSessionID + "." + Cookies.Open_Access_ID;
                    try
                    {
                        DateTime dt = RdsClient.Get<DateTime>(ky);
                        if (dt != null && dt > DateTime.Now)
                        {
                            )) RdsClient.Expire(ky, );
                            return Cookies.Open_Access_ID;
                        }
                    }
                    catch { }
                }
                string v = string.Empty;
                //冲突检测,如果冲突则重新分配
                )
                {
                    v = New_Redis_Session_ID;
                    ky = RedisKeysConfig.Key_CookieSessionID + "." + v;
                }
                Cookies.SetCookie(CookieNames.Open_Access_ID, v);
                RdsClient.Set<DateTime>(ky, DateTime.Now.AddHours(), DateTime.Now.AddHours());
                return v;
            }
        }
    }
LoginCache 添加用户缓存类
public class LoginCache
{
        const string key="Login_";
        public static bool LoginTest(int uid)
        {
            using (IRedisClient redis = RedisClientHelper.RdsClient)
            {
                return redis.Get<GP_Members_Model>(key+uid) != null;
            }
        }
        public static void LoginAdd(int uid, GP_User_Model v)
        {
            using (IRedisClient redis = RedisClientHelper.RdsClient)
            {
                redis.Set<GP_User_Model>(key+uid, v, TimeSpan.FromMinutes());
            }
        }
        public static void LoginRemove(int uid)
        {
            using (IRedisClient redis = RedisClientHelper.RdsClient)
            {
                redis.Remove(key+uid);
            }
        }
        public static GP_User_Model GetLoginCache(int uid)
        {
            using (IRedisClient redis = RedisClientHelper.RdsClient)
            {
                return redis.Get<GP_User_Model>(key+uid);
            }
        }
}

 

Web多客户端单点登录的更多相关文章

  1. 八幅漫画理解使用JSON Web Token设计单点登录系统

    用jwt这种token的验证方式,是不是必须用https协议保证token不被其他人拦截? 是的.因为其实只是Base64编码而已,所以很容易就被解码了.如果你的JWT被嗅探到,那么别人就可以相应地解 ...

  2. 八幅漫画理解使用 JSON Web Token 设计单点登录系统

    原文出处: John Wu 上次在<JSON Web Token – 在Web应用间安全地传递信息>中我提到了JSON Web Token可以用来设计单点登录系统.我尝试用八幅漫画先让大家 ...

  3. [转]八幅漫画理解使用JSON Web Token设计单点登录系统

    上次在<JSON Web Token - 在Web应用间安全地传递信息>中我提到了JSON Web Token可以用来设计单点登录系统.我尝试用八幅漫画先让大家理解如何设计正常的用户认证系 ...

  4. 使用JSON Web Token设计单点登录系统

    用户认证八步走 所谓用户认证(Authentication),就是让用户登录,并且在接下来的一段时间内让用户访问网站时可以使用其账户,而不需要再次登录的机制. 小知识:可别把用户认证和用户授权(Aut ...

  5. 使用JSON Web Token设计单点登录系统--转

    原文地址:https://leon_lizi.gitbooks.io/json-web-token/content/chapter2.html 用户认证八步走 所谓用户认证(Authenticatio ...

  6. 身份认证系统(二)多WEB应用的单点登录

    随着互联网的发展,web应用的复杂度也一直在提升,慢慢的单一的web应用已经不能满足复杂的业务需求.例如百度的搜索.新闻.百科.贴吧,其实本质上都是不同的网站.当用户使用这些平台的时候,我们当然不希望 ...

  7. 一图搞懂Web应用的单点登录

    单点登录即Signle Sign On,简称SSO.其解决的是用户在多个站点之间跳转时需要频繁登录的问题,比如用户登录了天猫,就应该无需再使用账号登录淘宝,它们之间是可以相互信任的,应该自动同步登录状 ...

  8. cas sso单点登录系列2:cas客户端和cas服务端交互原理动画图解,cas协议终极分析

    转:http://blog.csdn.net/ae6623/article/details/8848107 1)PPT流程图:ppt下载:http://pan.baidu.com/s/1o7KIlom ...

  9. 单点登录CAS使用记(二):部署CAS服务器以及客户端

    CAS-Server下载地址:https://www.apereo.org/projects/cas/download-cas CAS-Client下载地址:http://developer.jasi ...

随机推荐

  1. [置顶] ZK(The leading enterprise Ajax framework)入门指南

    1. Why ZK JavaEE领域从来就不缺少Framework尤其是Web Framework,光是比较流行的就有:SpringMVC.Struts2.JSF系列…… 其它不怎么流行的.小众的.非 ...

  2. iOS GCD中级篇 - dispatch_group

    1.关于dispatch_group 把一组任务提交到队列中,这些队列可以不相关,然后监听这组任务完成的事件. 最常见的几个方法: 1.dispatch_group_create创建一个调度任务组 2 ...

  3. YC(Y Combinator)斯坦福大学《如何创业》课程要点记录(粗糙)

    20节课程,每节都是干货满满,时常听说理论无用,但是好的理论,绝对能帮助你少走一些弯路. YC简介: Y Combinator成立于2005年,是美国著名创业孵化器,Y Combinator扶持初创企 ...

  4. 一起来玩echarts系列(二)------echarts图表自适应问题

    为了直观查看公司服务器各个进程占用的内存动态情况,我使用echarts进行数据可视化,具体的实现过程按下不表. 最后实现的效果如图: 然后问题就来了,因UI采用了Bootstrap响应式框架,所以除了 ...

  5. Maven3在Eclipse上安装插件

    eclipse 安装插件的方式最常见的有两种: 1. 一种是在线安装,这貌似是用的最多的,就是:Help -->  Install New Software,然后输入 HTTP 地址来安装,但有 ...

  6. C语言之逻辑运算符

    一 逻辑运算符: &&:逻辑与,读作并且 表达式左右两边都为真,那么结果才为真 口诀:一假则假 ||:逻辑或,读作或者 表达式左右两边,有一个为真,那么结果就为真 口诀:一真则真 !: ...

  7. GDKOI 2015 Day1 T2 单词统计Pascal

    我虽然没有参加GDKOI2015,但是我找了2015年的题练了一下. 题意如下: 思路:最大流,因为有多组数据,每次读入一组数据都要清零. a. 将每个点拆分成两个点,例如样例G→G`,再将字母一一编 ...

  8. NG2入门 - 架构

    AngularJS2 学习 继TypeScript之后,终于到了ng2的学习路程,同样学习根据angular官网文档进行,对文档中的内容根据自己的理解略有改动.看官可看官网文档,也可以看本系列博文 首 ...

  9. Josephus问题的不同实现方法与总结

    /************************************************************************/ /* Josephus问题--数组实现 */ /* ...

  10. linux面试题集锦3《转》

    三.简答题: 1.简述Linux文件系统通过i节点把文件的逻辑结构和物理结构转换的工作过程. 参考答案: Linux通过i节点表将文件的逻辑结构和物理结构进行转换. i节点是一个64字节长的表,表中包 ...