程序员的自我救赎---3.2:SSO及应用案例

《前言》html

(一) Winner2.0 框架基础分析前端

(二)PLSQL报表系统git

(三)SSO单点登陆github

(四) 短信中心与消息中心web

(五)钱包系统ajax

(六)GPU支付中心数据库

(七)权限系统跨域

(八)监控系统浏览器

(九)会员中心安全

(十) APP版本控制系统

(十一)Winner前端框架与RPC接口规范讲解

(十二)上层应用案例

(十三)总结

 

《SSO及应用案例》

先说说SSO(单点登陆)这种产物是怎么来的? 场景是这样的假设一个大型应用平台(web)下面有几个模块好比:商城,机票,酒店。

我上商城时候我没有登陆,则登陆一下,而又从商城跳转到酒店,发现酒店没登陆则又须要登陆一下酒店网站。

这里人们就会想有没有,我在我当前域名下一处登陆,就能够在当前域名(子域名)随处浏览以及操做,因此这时候就诞生了SSO。

 

SSO不仅仅解决了咱们“一处登陆,处处操做”的问题之外,还省去了咱们每一个项目开发登陆模块的时间。SSO的基本原理以下:

 

SSO虽然叫单点登陆,可是咱们更愿意叫他“统一登陆中心”,由于的他的职责就是承担了全部的登陆工做,虽然SSO在Web时代很盛行,可是在APP时代

就又有很大的不一样了,这个我后面会讲到。这里先讲一下上面一张图中SSO 以及客户端分别是作了哪些事情

 

 

 

 这里画的仍是比较抽象,有几点能够说一下:

1,上图中应用里用Session存储用户信息,有的作法是用Cookie。这里用Session或Cookie均可以,可是Cookie自己存在客户端浏览器中,

    因此从安全性上来讲不如Session,Cookie的优势是不会随浏览器的关闭而销毁,下次访问网站时能够无需登陆。这里各取所需,咱们从安全性

    上考虑说选择了用Session。

2,关于建立Ticket后将Ticket传给子站。Ticket叫作“令牌”,自己包含用户的基础信息(好比帐号、用户名)还有子站要访问的页面地址,以及过时时间等等。

   Ticket要回传给子站有的是直接往SSO站的Cookie里面存,而后子站经过设置domain参数共享cookie读写。这个自己没有问题可是仍是个第一点同样。

    把握好安全性就行。

3,图一我画的是用cookie的方式,尽管本地有用户信息(Ticket)可是为是安全仍是要上SSO上请验证一下,登陆是否过时。这种作法有的甚至每一个页面都去

    请求SSO看是否有过时,过时了则退出登陆,这个是根据业务需求的不一样作的。好比邮箱,没操做一次受权时间加长10分钟,若是十分钟没有任何操做

     再操做的时候就被退出了。这个看具体应用,用法不一样而已。

4,若是客户端(浏览器)禁用Cookie那Session是拿不到的。这个其实都知道每一个浏览器的Session_Id不一样,Session自己是键值对,可是惟一性标识

    不是Session的key,是Session_id,而session_id 是保存在浏览器的Cookie中的,其实就等于禁用了Cookie,Session也废了。

 

 

============================华丽的分割线===================================

 

接下来要说重点了,其实在上一篇《理解Oauth2.0》中就讲到了不少和SSO相似的概念,其实二者本质是同样的。可是咱们也能够

分开来看。我就更习惯分开来看,个人理解是这样的,我认为OAuth更关注的是“受权”,SSO则侧重是“登陆”。

因此从概念上来讲,OAuth的设计天生就不用去关注好比跨域这样的问题,SSO则更可能是本平台下一站登陆,随处操做。

 

前期咱们Winner框架中是SSO来扩展OAuth,今年Jason重构了一个版本则是OAuth来兼任SSO。这里没有好坏技术高低之分,只是场景不一样。

如今基本是一个APP的时代,因此SSO的功能被弱化了,更多时候咱们使用APP就没有一个所谓的“一处登陆,随处操做”的说法,就一个登陆。

 

咱们来看看Winner中的核心代码:

首先,我在前面讲《Winner.FrameWork.MVC》 的时候有说到,之前咱们使用基类去验证用户是否登陆,而如今咱们使用更灵活的特性类去处理

 

 咱们Winner中特性类的验证最经常使用的是[AuthLogin] 和 [AuthRight] 二者的不一样在于 [AuthLogin] 只验证是否有登陆,没有登陆就去登陆。

 意思就是说该页面全部人都有权限访问,前提是有注册。而[AuthRight] 则不仅仅是验证了是否登陆,还验证了是否有权限访问本页面。

关于权限那一块,在后面的文章中我再单独讲权限系统时再细讲。

 

咱们重点来看一下[AuthLogin] 的核心代码:

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using System.Xml;
using Winner.Framework.MVC.Attribute;
using Winner.Framework.MVC.GlobalContext;
using Winner.Framework.MVC.Models;
using Winner.Framework.MVC.Models.Account;
using Winner.Framework.Utils;

namespace Winner.Framework.MVC
{
    /// <summary>
    /// PC Web用户登录检查
    /// </summary>
    public class AuthLoginAttribute : AuthorizationFilterAttribute
    {
        /// <summary>
        /// 实例化一个新的验证对象
        /// </summary>
        /// <param name="ignore">是否忽略检查</param>
        public AuthLoginAttribute(bool ignore = false)
            : base(ignore)
        {
        }

        /// <summary>
        /// 登录验证
        /// </summary>
        /// <param name="context">当前上下文</param>
        protected override bool OnAuthorizationing(AuthorizationContext context)
        {
            //Ajax请求但又未登陆时则返回信息
            if (!ApplicationContext.Current.IsLogined && base.ContextProvider.IsAjaxRequest)
            {
                OutputResult("未登陆或者会话已过时,请从新登陆!", 401);
                return false;
            }
            if (context.HttpContext.Session == null)
            {
                throw new Exception("服务器Session不可用!");
            }
            try
            {
                //调用提供者进行登录
                ProviderManager.LoginProvider.Login();
            }
            catch (Exception ex)
            {
                Log.Error(ex);
                if (ex.InnerException != null)
                {
                    Log.Error(ex.InnerException);
                }
                OutputResult("登录时出现系统繁忙,请稍后再试!", 401);
                return false;
            }


            //若是没有登录则返回
            if (!ApplicationContext.Current.IsLogined)
            {
                OutputResult("未登陆", 401);
                return false;
            }
            return true;
        }
    }
}

 

 咱们看到一开始咱们有base(ignore);这个我在前面的篇章中有讲到过,这个能够经过配置文件配置,目的是省去咱们每一个项目开发的时候都要去登陆。

在配置文件中默认一个登陆帐号,这样调试时候能省不少时间。

咱们判断的步骤是这样的:

第一步:若是用户是ajax请求,而且用户信息不存在的话直接返回false。这里是应对用户登陆以后 用户长时间未操做形成用户信息过时失效

             由于咱们的Winner框架基本都是Ajax请求,因此当两个条件都存在的时候就直接返回401错误。若是界面显示401则从新刷新一下,

             由于刷新就不是Ajax了,因此就会跳到登陆页去登陆。

第二步:判断Session是否可用,不可用就直接抛异常了,就是我上面说的禁用Session这种状况。

第三步:在ProviderManager.LoginProvider.Login(),咱们才是作了具体的操做,咱们看一下Login()代码:

   public void Login()
        {
            //检查是否有SSO站点POST过来的用户退出数据
            string str = HttpContext.Current.Request.Url.Query;
            if (str.Contains("logout"))
            {
                //TODO:退出本地登录
                Logout();
                return;
            }
            //检查本地系统是否已登录
            if (ApplicationContext.Current.IsLogined)
                return;

            //判断是否有配置自动登录
            if (GlobalConfig.IsAutoLogin)
            {
                //代理登录配置文件所配置的用户
                var autoResult = ApplicationContext.UserLogin(GlobalConfig.DefaultAutoLoginUserId, true);
                if (!autoResult.Success)
                {
                    throw new Exception(autoResult.Message);
                }
                HttpCookie cookie = new HttpCookie("ticket");
                cookie.Value = GlobalConfig.DefaultAutoLoginToken;
                HttpContext.Current.Response.AppendCookie(cookie);
                return;
            }

            //若是没有Ticket直接跳转到SSO进行检查
            int userId;
            if (!ApplicationContext.GetNodeIdByTicket(out userId))
            {
                SSOLogin();
                return;
            }
            Log.Debug("user_id={0}", userId);
            //登录到本地系统
            var result = ApplicationContext.UserLogin(userId, false);
            if (!result.Success)
            {
                throw new Exception(result.Message);
            }
        }

 

  private void SSOLogin()
        {
            string service = HttpContext.Current.Request.Url.AbsoluteUri;
            service = Regex.Replace(service, @"\?ticket[^&]*.", "");
            string url = string.Concat(GlobalConfig.SSO_LoginURL, "?service=", HttpContext.Current.Server.UrlEncode(service));
            HttpContext.Current.Response.Redirect(url);
        }

 

这里ApplicationContext.Current.IsLogined为True的话,就是用户已经登陆过了,登陆过了就返回,IsLogined属性里面是判断了用户信息是否存在。

若是配置了自动登陆,则装载自动登陆的用户信息,从配置文件中读取。最后,上面判断都False的话,就跳到SSO系统去登陆获取ticket。

 

===================================华丽的分割线===========================

 

下面就是SSO系统作的事情,SSO最基本的职责就是登陆,首先就是登陆界面。根据用户填写的帐号密码判断用户是否注册,没有注册则注册。

说白了就是登陆注册流程。用户在SSO登陆成功以后则建立Session保存用户帐号,而后生成一个ticket字符串。每一个团队对于Ticket字符串的内容

都不太相同,可是大抵就是要请求界面的url,帐户号,受权码这些。

 

固然子系统判断URL中有ticket值的时候,就将Ticket 写入子项目的Session,其实咱们会有一个UserInfo的基础对象,这个Userinfo是一个用户信息的model。

这个是根据Ticket带过来用户帐户再到数据库查了一次的。

 

Jason重构一次SSO,方式上有点变更,更多的是采用Oauth2.0的方式。不清楚Oauth的能够看我上篇文章《理解Oauth2.0》

这里我公开一下咱们SSO项目的源码,因此我就不一一的贴出来了。有兴趣的朋友能够本身看代码不懂的能够在QQ群里咨询。

 

SSO登陆中心GitHub下载地址:https://github.com/demon28/OAuth2.git

 

就写到这里。有兴趣一块儿探讨Winner框架的能够加咱们QQ群:261083244。或者扫描左侧二维码加群。

相关文章
相关标签/搜索