• Asp.Net 单点登录(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的方式通知其他电脑下线。由于业务不需要,就没有增加即时通讯。感谢观看。

  • 相关阅读:
    重建Exchange邮件系统的系统邮箱
    枚举算法001
    关于网站备案的44个问题
    wireshack使用
    格言
    程序员遇到BUG的解释
    只要有信心任何事情都可以做成,今天表现不错哦,加油!
    踏实,自信
    学会经营自己的关系
    戒酒
  • 原文地址:https://www.cnblogs.com/BFMC/p/16250371.html
Copyright © 2020-2023  润新知