Asp.Net 單點登錄(SSO)|禁止重覆登陸|登錄強制下線

来源:https://www.cnblogs.com/BFMC/archive/2022/05/10/16250371.html
-Advertisement-
Play Games

背景: 先上個圖,看一下效果: 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的方式通知其他電腦下線。由於業務不需要,就沒有增加即時通訊。感謝觀看。

從前慢,車馬慢。 一生只愛一個人。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 現象: 最近將pyinsatller升級到最新的 Version: 5.0.1版本後(之前一直用的是3.5版本同樣方法未遇到問題,今次更新到最新版本後5.0.1後打包就遇到問題,具體是這中間哪個版本開始有變化也不清楚了,也不去追究,凡是在新版本中遇到問題就在新版本中解決),詳細現象及解決辦法如下: ...
  • 昨天突發奇想想來玩一玩,然後安裝了一下午才成功,基本所有該踩的坑都踩了,但當時沒截圖,現在靠著記憶寫一下。 官網鏈接:https://www.mongodb.com/try/download/community。 需要註意的是:超過次數就必須要登錄才能下載。(證明我真的試過很多次) 1.最開始出現的 ...
  • 今天某個項目的數據有些問題,需要查詢日誌看看具體的情況 結果在執行 cat ***.log |grep "關鍵字" 命令後包如下錯誤: grep: memory exhausted 思路1: 既然提示 記憶體問題,是不是日誌文件太大了, 用 du -sh * 命令查看 後文件也就 300M 思路2: ...
  • 下載&安裝Cmake 進入下載頁面 Download | CMake 選擇安裝包版本 打開安裝包,下一步之後選擇添加path 選擇完安裝文件夾開始安裝 下載&配置OpenCV 進入下載頁面 Releases - OpenCV 選擇版本下載(我下的是 Sources,Windows版是已經構建好的,不 ...
  • 多線程筆記(二) 1. Synchronized 和 Lock 的區別 synchronized是Java的關鍵字,是 JVM 層面的內置功能和實現。 Lock是一個介面,是代碼層面的實現 synchronized可以隱式的獲取,釋放鎖 lock是顯式的獲取,釋放鎖 synchronized在發生異 ...
  • 來源:編碼磚家 鏈接:cnblogs.com/xiaoyangjia/p/11267191.html 背景 最近頻繁出現慢SQL告警,執行時間最長的竟然高達5分鐘。導出日誌後分析,主要原因竟然是沒有命中索引和沒有分頁處理 。 其實這是非常低級的錯誤,我不禁後背一涼,團隊成員的技術水平亟待提高啊。改造 ...
  • 實現一個雷達掃描圖。 源代碼在TK_King/雷達 (gitee.com),自行下載就好了 製作思路 繪製圓形(或者稱之輪) 繪製分割線 繪製掃描範圍 添加掃描點 具體實現 首先我們使用自定義的控制項。你可以使用vs自動添加,也可以手動創建類。註意手動創建時要創建Themes/Generic.xaml ...
  • ODT文檔格式一種開放文檔格式(OpenDocument Text)。通常,ODT格式的文件可以使用LibreOffice Writer、MS Word或其他一些文檔編輯器來打開。我們在處理文檔時,可通過格式轉換的方式,將ODT轉為其他格式,或者將其他格式轉為ODT,來獲取目標文檔。本文,以C#及V ...
一周排行
    -Advertisement-
    Play Games
  • PasteSpider是什麼? 一款使用.net編寫的開源的Linux容器部署助手,支持一鍵發佈,平滑升級,自動伸縮, Key-Value配置,項目網關,環境隔離,運行報表,差量升級,私有倉庫,集群部署,版本管理等! 30分鐘上手,讓開發也可以很容易的學會在linux上部署你得項目! [從需求角度介 ...
  • SQLSugar是什麼 **1. 輕量級ORM框架,專為.NET CORE開發人員設計,它提供了簡單、高效的方式來處理資料庫操作,使開發人員能夠更輕鬆地與資料庫進行交互 2. 簡化資料庫操作和數據訪問,允許開發人員在C#代碼中直接操作資料庫,而不需要編寫複雜的SQL語句 3. 支持多種資料庫,包括但 ...
  • 在C#中,經常會有一些耗時較長的CPU密集型運算,因為如果直接在UI線程執行這樣的運算就會出現UI不響應的問題。解決這類問題的主要途徑是使用多線程,啟動一個後臺線程,把運算操作放在這個後臺線程中完成。但是原生介面的線程操作有一些難度,如果要更進一步的去完成線程間的通訊就會難上加難。 因此,.NET類 ...
  • 一:背景 1. 講故事 前些天有位朋友在微信上丟了一個崩潰的dump給我,讓我幫忙看下為什麼出現了崩潰,在 Windows 的事件查看器上顯示的是經典的 訪問違例 ,即 c0000005 錯誤碼,不管怎麼說有dump就可以上windbg開幹了。 二:WinDbg 分析 1. 程式為誰崩潰了 在 Wi ...
  • CSharpe中的IO+NPOI+序列化 文件文件夾操作 學習一下常見的文件、文件夾的操作。 什麼是IO流? I:就是input O:就是output,故稱:輸入輸出流 將數據讀入記憶體或者記憶體輸出的過程。 常見的IO流操作,一般說的是[記憶體]與[磁碟]之間的輸入輸出。 作用 持久化數據,保證數據不再 ...
  • C#.NET與JAVA互通之MD5哈希V2024 配套視頻: 要點: 1.計算MD5時,SDK自帶的計算哈希(ComputeHash)方法,輸入輸出參數都是byte數組。就涉及到字元串轉byte數組轉換時,編碼選擇的問題。 2.輸入參數,字元串轉byte數組時,編碼雙方要統一,一般為:UTF-8。 ...
  • CodeWF.EventBus,一款靈活的事件匯流排庫,實現模塊間解耦通信。支持多種.NET項目類型,如WPF、WinForms、ASP.NET Core等。採用簡潔設計,輕鬆實現事件的發佈與訂閱。通過有序的消息處理,確保事件得到妥善處理。簡化您的代碼,提升系統可維護性。 ...
  • 一、基本的.NET框架概念 .NET框架是一個由微軟開發的軟體開發平臺,它提供了一個運行時環境(CLR - Common Language Runtime)和一套豐富的類庫(FCL - Framework Class Library)。CLR負責管理代碼的執行,而FCL則提供了大量預先編寫好的代碼, ...
  • 本章將和大家分享在ASP.NET Core中如何使用高級客戶端NEST來操作我們的Elasticsearch。 NEST是一個高級別的Elasticsearch .NET客戶端,它仍然非常接近原始Elasticsearch API的映射。所有的請求和響應都是通過類型來暴露的,這使得它非常適合快速上手 ...
  • 參考delphi的代碼更改為C# Delphi 檢測密碼強度 規則(仿 google) 仿 google 評分規則 一、密碼長度: 5 分: 小於等於 4 個字元 10 分: 5 到 7 字元 25 分: 大於等於 8 個字元 二、字母: 0 分: 沒有字母 10 分: 全都是小(大)寫字母 20 ...