程序員的自我救贖---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。或者掃描左側二維碼加羣。

相關文章
相關標籤/搜索