Web多客户端单点登录
一 数据库
除了用户表之外,新建一个外联表<用户票据表> 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多客户端单点登录的更多相关文章
- 八幅漫画理解使用JSON Web Token设计单点登录系统
用jwt这种token的验证方式,是不是必须用https协议保证token不被其他人拦截? 是的.因为其实只是Base64编码而已,所以很容易就被解码了.如果你的JWT被嗅探到,那么别人就可以相应地解 ...
- 八幅漫画理解使用 JSON Web Token 设计单点登录系统
原文出处: John Wu 上次在<JSON Web Token – 在Web应用间安全地传递信息>中我提到了JSON Web Token可以用来设计单点登录系统.我尝试用八幅漫画先让大家 ...
- [转]八幅漫画理解使用JSON Web Token设计单点登录系统
上次在<JSON Web Token - 在Web应用间安全地传递信息>中我提到了JSON Web Token可以用来设计单点登录系统.我尝试用八幅漫画先让大家理解如何设计正常的用户认证系 ...
- 使用JSON Web Token设计单点登录系统
用户认证八步走 所谓用户认证(Authentication),就是让用户登录,并且在接下来的一段时间内让用户访问网站时可以使用其账户,而不需要再次登录的机制. 小知识:可别把用户认证和用户授权(Aut ...
- 使用JSON Web Token设计单点登录系统--转
原文地址:https://leon_lizi.gitbooks.io/json-web-token/content/chapter2.html 用户认证八步走 所谓用户认证(Authenticatio ...
- 身份认证系统(二)多WEB应用的单点登录
随着互联网的发展,web应用的复杂度也一直在提升,慢慢的单一的web应用已经不能满足复杂的业务需求.例如百度的搜索.新闻.百科.贴吧,其实本质上都是不同的网站.当用户使用这些平台的时候,我们当然不希望 ...
- 一图搞懂Web应用的单点登录
单点登录即Signle Sign On,简称SSO.其解决的是用户在多个站点之间跳转时需要频繁登录的问题,比如用户登录了天猫,就应该无需再使用账号登录淘宝,它们之间是可以相互信任的,应该自动同步登录状 ...
- cas sso单点登录系列2:cas客户端和cas服务端交互原理动画图解,cas协议终极分析
转:http://blog.csdn.net/ae6623/article/details/8848107 1)PPT流程图:ppt下载:http://pan.baidu.com/s/1o7KIlom ...
- 单点登录CAS使用记(二):部署CAS服务器以及客户端
CAS-Server下载地址:https://www.apereo.org/projects/cas/download-cas CAS-Client下载地址:http://developer.jasi ...
随机推荐
- hdu1198--并查集
Problem Description Benny has a spacious farm land to irrigate. The farm land is a rectangle, and is ...
- Android中Bitmap, Drawable, Byte之间的转化
1. Bitmap 转化为 byte ByteArrayOutputStream out = new ByteArrayOutputStream(); bitmap.compress(Bitmap. ...
- [C++STDlib基础]关于C标准输入输出的操作——C++标准库头文件<cstdio>
网上实例 总结 /* _STD_BEGIN using _CSTD clearerr; using _CSTD fclose; using _CSTD feof; using _CSTD ferror ...
- JS —— 数组与字符串方法
数组 1. 基本方法 1)Array.prototype.unshift(item1,item2,item3...) 描述: 在数组的前面添加元素,一次可以传入多个参数. 注意:元素会根据当前传入的 ...
- .Net程序员学用Oracle系列(8):触发器、任务、序列、连接
<.Net程序员学用Oracle系列:导航目录> 本文大纲 1.触发器 1.1.创建触发器 1.2.禁用触发器 & 启用触发器 & 删除触发器 2.任务 2.1.DBMS_ ...
- Golang爬虫示例包系列教程(一):pedaily.com投资界爬虫
Golang爬虫示例包 文件结构 自己用Golang原生包封装了一个爬虫库,源码见go get -u -v github.com/hunterhug/go_tool/spider ---- data ...
- IIS配置 Web部署 svg/woff/woff2字体 404错误
一,woff/woff2字体 404错误,这个可以在浏览器按F12的Console页面可以发现问题,如下图: 解决方法: 一,打开服务器IIS管理器,找到MIME类型 二,添加MIME类型 添加三条: ...
- WHAT?【 $.fn.extend() 】vs【 $.extend() 】
废话不多说,干货来了,转自http://www.cnblogs.com/hellman/p/4349777.html (function($){ $.fn.extend({ test:function ...
- Smail语法
1.数据类型 2.函数: Func-Name (Para-Type1Para-Type2Para-Type3...)Return-Type 注意参数与参数之间没有任何分隔符 3.函数调用: 4.关于s ...
- C# 实验4 数据库
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...