背景: 先上個圖,看一下效果: SSO英文全稱Single Sign On(單點登錄)。SSO是在多個應用系統中,用戶只需要登錄一次就可以訪問所有相互信任的應用系統。它包括可以將這次主要的登錄映射到其他應用中用於同一個用戶的登錄的機制。 它是目前比較流行的企業業務整合的解決方案之一。(本段內容來自百 ...
背景:
先上個圖,看一下效果:
SSO英文全稱Single Sign On(單點登錄)。SSO是在多個應用系統中,用戶只需要登錄一次就可以訪問所有相互信任的應用系統。它包括可以將這次主要的登錄映射到其他應用中用於同一個用戶的登錄的機制。
它是目前比較流行的企業業務整合的解決方案之一。(本段內容來自百度百科) 話不多說,開擼!
邏輯分析:
Client1:用戶A在電腦1上登錄管理員賬號
Service:驗證用戶A登陸成功生成Admin賬號的Token令牌,分別存儲電腦1的cookie中和伺服器的全局變數中(可以是session,緩存,全局變數,資料庫)
Client2:用戶B在電腦2上登錄管理員賬號
Service:驗證用戶B登陸成功重新生成Admin賬號的Token令牌,分別存儲電腦2的cookie中和伺服器的全局變數中(可以是session,緩存,全局變數,資料庫)
Client1:觸發驗證:
1,判斷伺服器全局變數是否過期,提示:身份信息過期,請重新登錄。
2,判斷客戶端的cookie是否過期,提示:長時間未登錄,已下線。
3,判斷電腦1上的cookie與伺服器全局變數相比是否一致。提示:此用戶已在別處登陸!你被強制下線!
代碼實現
Service:
1,創建一個伺服器校驗登錄類,代碼如下
using Coldairarrow.Business;
using Coldairarrow.Util;
using System;
using System.Text;
using System.Web.Mvc;
namespace Coldairarrow.Web
{
/// <summary>
/// 校驗登錄
/// </summary>
public class CheckLoginAttribute : FilterAttribute, IActionFilter
{
public IOperator _operator { get; set; }
public ILogger _logger { get; set; }
/// <summary>
/// Action執行之前執行
/// </summary>
/// <param name="filterContext">過濾器上下文</param>
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.RequestContext.HttpContext.Request;
try
{
//若為本地測試,則不需要登錄
if (GlobalSwitch.RunModel == RunModel.LocalTest)
{
return;
}
//判斷是否需要登錄
bool needLogin = !filterContext.ContainsAttribute<IgnoreLoginAttribute>();
//獲取session裡面的用戶id
var uid = SessionHelper.Session["UserId"]?.ToString();
if (needLogin)
{
if (string.IsNullOrEmpty(uid))
{
//轉到登錄
RedirectToLogin();
}
else
{
var Cguid = filterContext.HttpContext.Request.Cookies["CToken"].Value?.ToString();
var Sguid = CacheHelper.Cache.GetCache(uid + "_SToken")?.ToString();
//判斷是否過期
if (string.IsNullOrEmpty(Cguid) || string.IsNullOrEmpty(Sguid))
{
// 過期 轉到登錄
ReturnLogin("身份信息以失效,請重新登陸!");
SessionHelper.Session["UserId"] = "";
}
else
{
//判斷用戶是否重覆登陸
if (Sguid != Cguid)
{
// 過期 轉到登錄
ReturnLogin("此用戶已在別處登陸!你被強制下線!");
SessionHelper.Session["UserId"] = "";
//message = "已登陸";
}
}
}
}
//if (needLogin && !_operator.Logged())
//{ //轉到登錄
// RedirectToLogin();
//}
//else
//{
// string Id = _operator.UserId;
// _operator.Login(Id);
// return;
//}
}
catch (Exception ex)
{
_logger.Error(ex);
RedirectToLogin();
}
void RedirectToLogin()
{
if (request.IsAjaxRequest())
{
filterContext.Result = new ContentResult
{
Content = new AjaxResult { Success = false, ErrorCode = 1, Msg = "未登錄" }.ToJson(),
ContentEncoding = Encoding.UTF8,
ContentType = "application/json"
};
}
else
{
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
string loginUrl = urlHelper.Content("~/Home/Login");
string script = $@"
<html>
<script>
top.location.href = '{loginUrl}';
</script>
</html>
";
filterContext.Result = new ContentResult { Content = script, ContentType = "text/html", ContentEncoding = Encoding.UTF8 };
}
}
void ReturnLogin(string msg)
{
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
string loginUrl = urlHelper.Content("~/Home/Login");
string script = $@"
<html>
<script>
alert('{msg}');
top.location.href = '{loginUrl}';
</script>
</html>
";
filterContext.Result = new ContentResult { Content = script, ContentType = "text/html", ContentEncoding = Encoding.UTF8 };
}
}
/// <summary>
/// Action執行完畢之後執行
/// </summary>
/// <param name="filterContext"></param>
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
}
2,創建一個mvc基控制器繼承Controller並且引用特性【CheckLogin】
3,業務控制器繼承BaseMvcController,並編寫登錄代碼。登陸成功後調用login方法,代碼如下:
/// <summary>
/// 登錄
/// </summary>
/// <param name="userId">用戶邏輯主鍵Id</param>
public void Login(string userId)
{
//保存登陸成功的令牌
string Guid_str = "";
//分配一個唯一標識符
Guid_str = GuidHelper.GuidTo16String();
HttpContext.Current.Response.Cookies["CToken"].Value = Guid_str;
//給系統變數存儲一個值,Uid代表哪個用戶,GUID則是唯一標識符
CacheHelper.Cache.SetCache(userId + "_SToken", Guid_str, new TimeSpan(0, 0, 30, 0, 0), ExpireType.Absolute);
SessionHelper.Session["UserId"] = userId;
}
4,這個時候基本就結束了,還需要增加一個忽略驗證的類,這個特性加在登錄頁面。意思是登錄頁面不需要觸發驗證;
5,伺服器驗證的核心代碼有點不優雅,不過實現邏輯了。有問題可以評論區溝通一下。本人用的是將token分別存儲在伺服器緩存+客戶端cookie完成 ,大家伺服器上可以用session,緩存,全局變數,資料庫等任意方式實現;
總結:
當用戶沒有重覆登陸時,系統分配一個guid給用戶,並記錄用戶id和對應的guid,這個用戶線上時系統變數存儲的用戶id以及對應的guid值是不會變的,這時候有另外一個人用相同的賬號登陸時,會改變系統變數中用戶id對應的guid。
這時候伺服器就判斷出系統變數存儲的guid與用戶cookie存儲的guid不同時,就會強制用戶下線。
這個可以升級為指定N台設備登錄,並且可以增加socket的方式通知其他電腦下線。由於業務不需要,就沒有增加即時通訊。感謝觀看。
從前慢,車馬慢。 一生只愛一個人。