实验环境配置

HOST文件配置如下:

127.0.0.1 app.com
127.0.0.1 sso.com

IIS配置如下:

应用程序池采用.Net Framework 4.0

注意IIS绑定的域名,两个完全不同域的域名。

app.com网站配置如下:

sso.com网站配置如下:

memcached缓存:

数据库配置:

数据库采用EntityFramework 6.0.0,首次运行会自动创建相应的数据库和表结构。

授权验证过程演示:

在浏览器地址栏中访问:http://app.com,如果用户还未登陆则网站会自动重定向至:http://sso.com/passport,同时通过QueryString传参数的方式将对应的AppKey应用标识传递过来,运行截图如下:

URL地址:http://sso.com/passport?appkey=670b14728ad9902aecba32e22fa4f6bd&username=

输入正确的登陆账号和密码后,点击登陆按钮系统自动301重定向至应用会掉首页,毁掉成功后如下所示:

由于在不同的域下进行SSO授权登陆,所以采用QueryString方式返回授权标识。同域网站下可采用Cookie方式。由于301重定向请求是由浏览器发送的,所以在如果授权标识放入Handers中的话,浏览器重定向的时候会丢失。重定向成功后,程序自动将授权标识写入到Cookie中,点击其他页面地址时,URL地址栏中将不再会看到授权标示信息。Cookie设置如下:

 

登陆成功后的后续授权验证(访问其他需要授权访问的页面):

校验地址:http://sso.com/api/passport?sessionkey=xxxxxx&remark=xxxxxx

返回结果:true,false

客户端可以根据实际业务情况,选择提示用户授权已丢失,需要重新获得授权。默认自动重定向至SSO登陆页面,即:http://sso.com/passport?appkey=670b14728ad9902aecba32e22fa4f6bd&username=seo@ljja.cn 同时登陆页面邮箱地址文本框会自定补全用户的登陆账号,用户只需输入登陆密码即可,授权成功后会话有效期自动延长一年时间。

SSO数据库验证日志:

用户授权验证日志:

用户授权会话Session:

数据库用户账号和应用信息:

应用授权登陆验证页面核心代码:

  1 /// <summary>
  2     ///  公钥:AppKey
  3     ///  私钥:AppSecret
  4     ///  会话:SessionKey
  5     /// </summary>
  6     public class PassportController : Controller
  7     {
  8         private readonly IAppInfoService _appInfoService = new AppInfoService();
  9         private readonly IAppUserService _appUserService = new AppUserService();
 10         private readonly IUserAuthSessionService _authSessionService = new UserAuthSessionService();
 11         private readonly IUserAuthOperateService _userAuthOperateService = new UserAuthOperateService();
 12
 13         private const string AppInfo = "AppInfo";
 14         private const string SessionKey = "SessionKey";
 15         private const string SessionUserName = "SessionUserName";
 16
 17         //默认登录界面
 18         public ActionResult Index(string appKey = "", string username = "")
 19         {
 20             TempData[AppInfo] = _appInfoService.Get(appKey);
 21
 22             var viewModel = new PassportLoginRequest
 23             {
 24                 AppKey = appKey,
 25                 UserName = username
 26             };
 27
 28             return View(viewModel);
 29         }
 30
 31         //授权登录
 32         [HttpPost]
 33         public ActionResult Index(PassportLoginRequest model)
 34         {
 35             //获取应用信息
 36             var appInfo = _appInfoService.Get(model.AppKey);
 37             if (appInfo == null)
 38             {
 39                 //应用不存在
 40                 return View(model);
 41             }
 42
 43             TempData[AppInfo] = appInfo;
 44
 45             if (ModelState.IsValid == false)
 46             {
 47                 //实体验证失败
 48                 return View(model);
 49             }
 50
 51             //过滤字段无效字符
 52             model.Trim();
 53
 54             //获取用户信息
 55             var userInfo = _appUserService.Get(model.UserName);
 56             if (userInfo == null)
 57             {
 58                 //用户不存在
 59                 return View(model);
 60             }
 61
 62             if (userInfo.UserPwd != model.Password.ToMd5())
 63             {
 64                 //密码不正确
 65                 return View(model);
 66             }
 67
 68             //获取当前未到期的Session
 69             var currentSession = _authSessionService.ExistsByValid(appInfo.AppKey, userInfo.UserName);
 70             if (currentSession == null)
 71             {
 72                 //构建Session
 73                 currentSession = new UserAuthSession
 74                 {
 75                     AppKey = appInfo.AppKey,
 76                     CreateTime = DateTime.Now,
 77                     InvalidTime = DateTime.Now.AddYears(1),
 78                     IpAddress = Request.UserHostAddress,
 79                     SessionKey = Guid.NewGuid().ToString().ToMd5(),
 80                     UserName = userInfo.UserName
 81                 };
 82
 83                 //创建Session
 84                 _authSessionService.Create(currentSession);
 85             }
 86             else
 87             {
 88                 //延长有效期,默认一年
 89                 _authSessionService.ExtendValid(currentSession.SessionKey);
 90             }
 91
 92             //记录用户授权日志
 93             _userAuthOperateService.Create(new UserAuthOperate
 94             {
 95                 CreateTime = DateTime.Now,
 96                 IpAddress = Request.UserHostAddress,
 97                 Remark = string.Format("{0} 登录 {1} 授权成功", currentSession.UserName, appInfo.Title),
 98                 SessionKey = currentSession.SessionKey
 99             }); 104
105             var redirectUrl = string.Format("{0}?SessionKey={1}&SessionUserName={2}",
106                 appInfo.ReturnUrl,
107                 currentSession.SessionKey,
108                 userInfo.UserName);
109
110             //跳转默认回调页面
111             return Redirect(redirectUrl);
112         }
113     }

Memcached会话标识验证核心代码:

public class PassportController : ApiController
    {
        private readonly IUserAuthSessionService _authSessionService = new UserAuthSessionService();
        private readonly IUserAuthOperateService _userAuthOperateService = new UserAuthOperateService();

        public bool Get(string sessionKey = "", string remark = "")
        {
            if (_authSessionService.GetCache(sessionKey))
            {
                _userAuthOperateService.Create(new UserAuthOperate
                {
                    CreateTime = DateTime.Now,
                    IpAddress = Request.RequestUri.Host,
                    Remark = string.Format("验证成功-{0}", remark),
                    SessionKey = sessionKey
                });

                return true;
            }

            _userAuthOperateService.Create(new UserAuthOperate
            {
                CreateTime = DateTime.Now,
                IpAddress = Request.RequestUri.Host,
                Remark = string.Format("验证失败-{0}", remark),
                SessionKey = sessionKey
            });

            return false;
        }
    }
 

Client授权验证Filters Attribute

public class SSOAuthAttribute : ActionFilterAttribute
    {
        public const string SessionKey = "SessionKey";
        public const string SessionUserName = "SessionUserName";

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var cookieSessionkey = "";
            var cookieSessionUserName = "";

            //SessionKey by QueryString
            if (filterContext.HttpContext.Request.QueryString[SessionKey] != null)
            {
                cookieSessionkey = filterContext.HttpContext.Request.QueryString[SessionKey];
                filterContext.HttpContext.Response.Cookies.Add(new HttpCookie(SessionKey, cookieSessionkey));
            }

            //SessionUserName by QueryString
            if (filterContext.HttpContext.Request.QueryString[SessionUserName] != null)
            {
                cookieSessionUserName = filterContext.HttpContext.Request.QueryString[SessionUserName];
                filterContext.HttpContext.Response.Cookies.Add(new HttpCookie(SessionUserName, cookieSessionUserName));
            }

            //从Cookie读取SessionKey
            if (filterContext.HttpContext.Request.Cookies[SessionKey] != null)
            {
                cookieSessionkey = filterContext.HttpContext.Request.Cookies[SessionKey].Value;
            }

            //从Cookie读取SessionUserName
            if (filterContext.HttpContext.Request.Cookies[SessionUserName] != null)
            {
                cookieSessionUserName = filterContext.HttpContext.Request.Cookies[SessionUserName].Value;
            }

            if (string.IsNullOrEmpty(cookieSessionkey) || string.IsNullOrEmpty(cookieSessionUserName))
            {
                //直接登录
                filterContext.Result = SsoLoginResult(cookieSessionUserName);
            }
            else
            {
                //验证
                if (CheckLogin(cookieSessionkey, filterContext.HttpContext.Request.RawUrl) == false)
                {
                    //会话丢失,跳转到登录页面
                    filterContext.Result = SsoLoginResult(cookieSessionUserName);
                }
            }

            base.OnActionExecuting(filterContext);
        }

        public static bool CheckLogin(string sessionKey, string remark = "")
        {
            var httpClient = new HttpClient
            {
                BaseAddress = new Uri(ConfigurationManager.AppSettings["SSOPassport"])
            };

            var requestUri = string.Format("api/Passport?sessionKey={0}&remark={1}", sessionKey, remark);

            try
            {
                var resp = httpClient.GetAsync(requestUri).Result;

                resp.EnsureSuccessStatusCode();

                return resp.Content.ReadAsAsync<bool>().Result;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private static ActionResult SsoLoginResult(string username)
        {
            return new RedirectResult(string.Format("{0}/passport?appkey={1}&username={2}",
                    ConfigurationManager.AppSettings["SSOPassport"],
                    ConfigurationManager.AppSettings["SSOAppKey"],
                    username));
        }
    }

示例SSO验证特性使用方法:

[SSOAuth]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
    }

更多.net技术前沿资讯请关注近乎/Spacebuilder》》

ASP.NET MVC SSO单点登录设计与实现(转载)的更多相关文章

  1. ASP.NET MVC SSO单点登录设计与实现

    实验环境配置 HOST文件配置如下: 127.0.0.1 app.com127.0.0.1 sso.com IIS配置如下: 应用程序池采用.Net Framework 4.0 注意IIS绑定的域名, ...

  2. ASP.NET MVC SSO 单点登录设计与实现

    实验环境配置 HOST文件配置如下: 127.0.0.1 app.com127.0.0.1 sso.com IIS配置如下: 应用程序池采用.Net Framework 4.0 注意IIS绑定的域名, ...

  3. SSO单点登录设计

    关键字: 单点登录 SSO Session 单点登录在现在的系统架构中广泛存在,他将多个子系统的认证体系打通,实现了一个入口多处使用,而在架构单点登录时,也会遇到一些小问题,在不同的应用环境中可以采用 ...

  4. .NET Core2.0+MVC 用Redis/Memory+cookie实现的sso单点登录

    之前发布过使用session+cookie实现的单点登录,博主个人用的很不舒服,为什么呢,博主自己测试的时候,通过修改host的方法,在本机发布了三个站点,但是,经过测试,发现,三个站点使用的sess ...

  5. Atitit.单向sso  单点登录的设计与实现

    Atitit.单向sso  单点登录的设计与实现 1. 单点登录sso 的三大解决方案1 2. 新方案:密码管理器方案1 3. 调用方1 4. 自动登录登录2 5. 主页跳转2 1. 单点登录sso  ...

  6. NET Core 2.0使用Cookie认证实现SSO单点登录

    NET Core 2.0使用Cookie认证实现SSO单点登录 之前写了一个使用ASP.NET MVC实现SSO登录的Demo,https://github.com/bidianqing/SSO.Sa ...

  7. CAS实现SSO单点登录原理

    1.      CAS 简介 1.1.  What is CAS ? CAS ( Central Authentication Service ) 是 Yale 大学发起的一个企业级的.开源的项目,旨 ...

  8. php sso单点登录原理阐述

    原理:就是用户登录了单点登录系统(sso)之后,就可以免登录形式进入相关系统: 实现: 点击登录跳转到SSO登录页面并带上当前应用的callback地址 登录成功后生成COOKIE并将COOKIE传给 ...

  9. CAS实现SSO单点登录原理(转)

    1.      CAS 简介 1.1.  What is CAS ? CAS ( Central Authentication Service ) 是 Yale 大学发起的一个企业级的.开源的项目,旨 ...

随机推荐

  1. Direct2D教程(外篇)环境配置

    2014年世界杯首场淘汰赛马上开始了,闲着没事,整理以前的博客草稿打发时间,意外的发现这篇文章,本来是打算加入到Direct2D那个系列的,不知道为什么把它给遗漏了.环境配置,对于熟手来说,不是什么重 ...

  2. Identity自增序列/唯一断标识

    ThreadStatic应用(Identity补完) 用于在高并发环境中的自增序列维护和快速创建唯一不重复的短标识,该类是线程安全的 如在ORM组件中,创建唯一的参数名 特点: 高并发环境下的性能保证 ...

  3. 锋利的JQuery —— DOM操作

    图片猛戳链接

  4. Atitit.软件中见算法 程序设计五大种类算法

    Atitit.软件中见算法 程序设计五大种类算法 1. 算法的定义1 2. 算法的复杂度1 2.1. Algo cate2 3. 分治法2 4. 动态规划法2 5. 贪心算法3 6. 回溯法3 7. ...

  5. Atitit  循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate).

    Atitit  循环(loop), 递归(recursion), 遍历(traversal), 迭代(iterate). 1.1. 循环算是最基础的概念, 凡是重复执行一段代码, 都可以称之为循环. ...

  6. Atitit 拦截数据库异常的处理最佳实践

    Atitit 拦截数据库异常的处理最佳实践 需要特殊处理的ex 在Dao层异常转换并抛出1 Server层转换为业务异常1 需要特殊处理的ex 在Dao层异常转换并抛出 } catch (SQLExc ...

  7. Atitit 图像清晰度 模糊度 检测 识别 评价算法 原理

    Atitit 图像清晰度 模糊度 检测 识别 评价算法 原理 1.1. 图像边缘一般都是通过对图像进行梯度运算来实现的1 1.2. Remark: 1 1.3.  1.失焦检测. 衡量画面模糊的主要方 ...

  8. VS2013模块对于SAFESEH映像是不安全的解决方法

    常见报错:error LNK2026: 模块对于 SAFESEH 映像是不安全的 解决方法:右键打开项目属性 -> 链接器 -> 命令行 -> 其他选项 (D) 中加入  /SAFE ...

  9. 使用SQL Server Audit记录数据库变更

        最近工作中有一个需求,就是某一个比较重要的业务表经常被莫名其妙的变更.在SQL Server中这类工作如果不事前捕获记录的话,无法做到.对于捕获变更来说,可以考虑的选择包括Trace,CDC. ...

  10. Android入门(八)广播

    原文链接:http://www.orlion.ga/572/ 一.广播机制 Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接收到自己所关心的广播内容,这些广 播可能是 ...