Asp.net單點登錄解決方案


Asp.net單點登錄解決方案

吳劍 2009-06-24

原創文章,轉載必需注明出處:http://www.cnblogs.com/wu-jian

 

前言

本文為09年寫的一篇老文章,詳細介紹了單點登錄(SSO)的設計、流程、實現關鍵點,以及Asp.net代碼演示。最近閑暇將文章進行了整理,重畫了UML,重寫了DEMO中的代碼。作為學習備忘,如能給人予幫助,不甚榮幸。同時個人能力有限,文中不足之處還請及時指正。

為方便描述,首先定義幾個統一名詞,文中出現之處均為如下含義。

主站:Passport集中驗證服務器,DEMO中為:http://www.passport.com/

分站:http://www.a.com/http://www.b.com/http://www.c.com/ 

憑證:用戶登錄后產生的鑒權標識,用於識別授權用戶。可為多種方式,DEMO中主站使用的是Cache,分站使用的是Session。

令牌:由Passport頒發可在各分站中流通的用戶唯一鑒權標識,DEMO中使用的是Cookie。

 

核心邏輯

使用集中式身份驗證,用戶數據存放於Passport,各分站統一使用Passport進行登錄和鑒權,如<圖1>所示:

<圖1>

<圖2>

<圖2>詳細描述了單點登錄邏輯與流程

流程一:匿名用戶訪問分站a

匿名用戶訪問分站a上的一個授權頁面,首先跳轉到主站通過帳號、密碼進行登錄鑒權,驗證通過后產生主站憑證,同時產生令牌,跳轉回分站a。

此時分站a檢測到用戶已持有令牌,於是用令牌再次去主站獲取用戶憑證,獲取成功后允許用戶訪問該授權頁面。同時產生分站a的本地憑證。

當該用戶需要再次驗證時將使用本地憑證,以減少網絡交互。

流程二:在分站a登錄的用戶訪問分站b

因為用戶在分站a登錄過,已持有令牌,所以分站b會用令牌去主站獲取用戶憑證,獲取成功后允許用戶訪問授權頁面。同時產生分站b的本地憑證。

 

實現關鍵點

令牌

令牌由主站頒發,主站頒發令牌同時生成用戶憑證,並記錄令牌與用戶憑證之間的對應關系,以根據用戶提供的令牌響應對應的憑證;

令牌要在各跨域分站中進行流通,DEMO中使用了Cookie,並指定Cookie.Domain="passport.com";

各分站如何共享主站的Cookie?從分站Redirect到主站頁面,然后該頁面讀取Cookie並以URL參數方式回傳即可,可在DEMO代碼中查看詳細實現。

//產生令牌
HttpCookie tokenCookie = new HttpCookie("Passport.Token");
tokenCookie.Domain = "passport.com";
//可使用自定義算法避免Cookie非法復制
//tokenCookie.Values.Add("Key", "加密算法");
tokenCookie.Values.Add("Value", tokenValue);
Response.AppendCookie(tokenCookie); 

在之前的文章中有讀者提到令牌(Cookie)被非法復制導致的安全問題,在此簡單說明一下。

首先默認設置了Cookie為關閉瀏覽器即失效,也就是說用戶在成功鑒權后持續打開瀏覽器的前提下才會導至令牌泄漏。

然后在Passport令牌的設計中有“過期時間”一項,也可以通過使用令牌的過期時間來確保令牌安全。

如果您覺得還不夠,其實只需要在令牌驗證時添加一些自定義邏輯,比如用時間、用戶特征產生一個Hash值作為令牌的安全KEY。

文章主要針對SSO的邏輯,一些細節上的代碼並未十全十美,讀者可以根據實際需求進行完善。

 

主站憑證

主站憑證是一個關系表,包含了三個字段:令牌、用戶憑證、過期時間。

主站憑證有多種實現方式可供選擇,比如要求可靠的話可以使用數據庫,要求性能的話可以使用Cache,DEMO中我使用的是Cache。如下代碼所示:

/// <summary>
/// 初始化緩存數據結構
/// </summary>
/// <remarks>
/// ----------------------------------------------------
/// | token(令牌) | cert(用戶憑證) | timeout(過期時間) |
/// |--------------------------------------------------|
/// </remarks>
private static void cacheInit()
{
    if (HttpContext.Current.Cache["PASSPORT.TOKEN"] == null)
    {
        DataTable dt = new DataTable();

        dt.Columns.Add("token", Type.GetType("System.String"));
        dt.Columns["token"].Unique = true;

        dt.Columns.Add("cert", Type.GetType("System.Object"));
        dt.Columns["cert"].DefaultValue = null;

        dt.Columns.Add("timeout", Type.GetType("System.DateTime"));
        dt.Columns["timeout"].DefaultValue = DateTime.Now.AddMinutes(double.Parse(System.Configuration.ConfigurationManager.AppSettings["Timeout"]));

        DataColumn[] keys = new DataColumn[1];
        keys[0] = dt.Columns["token"];
        dt.PrimaryKey = keys;

        //Cache的過期時間為 令牌過期時間*2
        HttpContext.Current.Cache.Insert("PASSPORT.TOKEN", dt, null, DateTime.MaxValue, TimeSpan.FromMinutes(double.Parse(System.Configuration.ConfigurationManager.AppSettings["Timeout"]) * 2));
    }
}

 

分站憑證

分站憑證主要用於減少重復驗證時網絡的交互,比如用戶已在分站a上登錄過,當他再次訪問分站a時,就不必使用令牌去主站驗證了,因為分站a已有該用戶的憑證。

分站憑證相對比較簡單,使用Session、Cookie均可。

 

分站SSO頁面基類

分站使用SSO的頁面會做一系列的邏輯判斷處理,如<圖2>所示。如果為每個頁面寫一遍這樣的邏輯會非常繁瑣。OK,那么把這套邏輯封裝成一個基類,凡是要使用SSO的頁面繼承該基類即可。如下代碼所示:

public class AuthBase : System.Web.UI.Page
{
    protected override void OnLoad(EventArgs e)
    {
        if (Session["A.Cert"] != null)
        {
            //分站憑證存在
            Response.Write("恭喜,分站憑證存在,您被授權訪問該頁面!");
        }
        else
        {
            //令牌驗證結果返回
            if (Request.QueryString["token"] != null)
            {
                //持有令牌
                if (Request.QueryString["token"] != "$token$")
                {
                    string tokenValue = Request.QueryString["token"];
                    //調用WebService獲取主站憑證
                    //防止令牌偽造
                    //此處還可使用公鑰私鑰的非對稱加密策略
                    SSO.SiteA.ServiceReference1.PassportServiceSoapClient passportService = new SSO.SiteA.ServiceReference1.PassportServiceSoapClient();
                    object cert = passportService.TokenGetCert(tokenValue);
                    if (cert != null)
                    {
                        //令牌正確
                        Session["A.Cert"] = cert;
                        Response.Write("恭喜,令牌存在,您被授權訪問該頁面!");
                    }
                    else
                    {
                        //令牌錯誤,去Passport登錄
                        Response.Redirect(SSO.Common.Tools.TokenReplace());
                    }
                }
                //未持有令牌,去Passport登錄
                else
                {
                    Response.Redirect(SSO.Common.Tools.TokenReplace());
                }
            }
            //未進行令牌驗證,去Passport驗證
            else
            {
                //當前url附加上token參數
                Response.Redirect(SSO.Common.Tools.TokenUrl());
            }
        }

        base.OnLoad(e);
    }

}//end class


用戶退出

<圖3>

用戶退出時分別清空主站令牌/憑證與所有分站憑證。DEMO中通過調用WebService清空該用戶的主站令牌/憑證,通過iframe清空各分站憑證,請參閱DEMO中的詳細代碼實現。

 

主站過期令牌/憑證清除

讀者可自行實現該邏輯,定時清除(DataTable)Cache[“PASSPORT.TOKEN”]中timeout字段超過當前時間的記錄。

 

DEMO

DEMO開發環境

.Net Framework 4.0

Visual Studio 2012

 

在IIS中配置站點

配置4個站點指向相應的目錄,並分別指定4個站點的主機頭: 

http://www.passport.com/ 

http://www.a.com/ 

http://www.b.com/ 

http://www.c.com/ 

 

修改hosts文件以將域名解析到本地站點

127.0.0.1          http://www.passport.com/

127.0.0.1          http://www.a.com/

127.0.0.1          http://www.b.com/

127.0.0.1          http://www.c.com/ 

 

DEMO下載

http://files.cnblogs.com/wu-jian/wujian_sso.rar

 

<全文完>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM