进公司以来, 所做的产品中, 下面的子系统就没有少于10个的, 其中有的是.net做的, 有的是java做的, 还有安卓端, ios端. 那么这么多子系统, 我可能需要访问其中的多个(同一平台), 我是否需要登录多次来操作呢? 这样是不是太不方便了. 在我们登录qq之后, 进入qq邮箱, 也并没有让我们多登录一次, 而是直接进去了. 那么在我们的这些系统中, 怎么来实现这种功能呢?
到这里, 就引出今天的主题 - SSO 单点登录. 在我们登录系统A之后, 去访问系统B, 此时并不需要再去登陆一次了. 这里有一个前提, 就是不论系统A还是系统B, 用的都是同一个账户和密码. 如果他们之间的用户名和密码不相同, 就麻烦很多了.
在网上搜到的单点登录解决方案, 比较多的是CAS, 耶鲁大学弄得一个单点登录解决方案。当然,在此篇,并不会介绍到CAS,只是通过一个小例子,来看看轮廓而已。
一、效果展示
在这里,我有三个网站, 当我在Server这个网站登录之后,ClientA,ClientB我直接输入backUrl后面的地址, 看看是否要让我登录。
输入之后, 让我直接进去了, 而没有让我再次登录。
当我在HomeA页面登出之后, 再去刷新其他的两个页面来看。
其他两个页面,也被迫登出了。
从效果上看, 是不是一个系统登录, 多个系统可以免登录了, 一处登出, 多个系统都被登出。 这就是一个单点登录的效果了。
二、关键代码
服务器登录部分:
[NoLogin] public ActionResult LoginCheck(string userName, string userPwd) { if (userName == "admin" && userPwd == "123456") { User user = new User { Guid = Guid.NewGuid().ToString(), Name = "admin", Pwd = "123456" }; var newCookie = new HttpCookie("userCode", user.Guid); newCookie.Expires = DateTime.Now.AddDays(1); Response.Cookies.Add(newCookie); RedisCache.Instance.Set(user.Guid, user, TimeSpan.FromMinutes(1)); return Json(new { status = "ok", guid = user.Guid }); } return Json(new { status = "notMatch" }); }
这里的NoLogin就是一个空的特性,里面啥都没有,可以放在类和方法上,用来判断是否不需要登录就可以直接访问页面。
当验证成功登录后, 将生成一个凭据Guid, 一会用户手持这个凭据来系统验证, 我这边就只看redis缓存中是否有这个缓存存在, 有则表示已经登录。并没有做更多的验证了。
我这里的redis是部署在ubuntu虚拟机上面的, redis对windows的支持并不是很好, 但是也能找到windows的版本, 如果可能的话, 我还是建议使用 linux 部署 redis。
客户端验证部分:
public class SSOClientAttribute : AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { //判断请求的方法或者控制器上, 有没有免登陆特性 if (filterContext.ActionDescriptor.IsDefined(typeof(NoLoginAttribute), false) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(NoLoginAttribute), false)) { return; } //需要登陆的方法, 继续验证 base.OnAuthorization(filterContext); } protected override bool AuthorizeCore(HttpContextBase httpContext) { HttpCookie cookie = httpContext.Request.Cookies["userCode"]; if (cookie == null || string.IsNullOrEmpty(cookie.Value)) return false; var user = RedisCache.Instance.GetOrDefault<User>(cookie.Value); if (user != null) { cookie.Expires = DateTime.Now.AddDays(1); httpContext.Response.SetCookie(cookie); RedisCache.Instance.Set(user.Guid, user, TimeSpan.FromMinutes(10)); return true; } return false; } protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { string loginUrl = ConfigurationManager.AppSettings["LoginUrl"]; string Url = filterContext.RequestContext.HttpContext.Request.Url.AbsoluteUri; loginUrl += "?backUrl=" + Url; filterContext.HttpContext.Response.Redirect(loginUrl, true); } }
登出部分就简单了, 只要清除cookie和redis缓存就行了。代码就不贴了。
三、结束语
CAS的单点登录比这个复杂多了, 这里只是我自己弄得一个小Demo,只是对单点登录的一个初步认识,后面有机会的话, 希望我会把Cas的搭建、使用方法或者解析贴出来。
参考:
蔡的mysso例子(以前看过他的例子,我写的更简单点,能实现效果就成了)