單點登入在ASP.NET上的簡單實現

來源:互聯網
上載者:User
asp.net 系統的基本架構

  我們假設一個系統System包含Service客戶服務中心、Shop網上購物中心和Office網上辦公中心三個獨立的網站。Service管理客戶的資料,登入和登出過程。不論客戶訪問System的任何一個頁面,系統都會轉到登入介面,在使用者登入後,系統會自動轉會到客戶上次請求的頁面。並且使用者此後可以在System中無縫切換。不需要再次進行登入。即在System中實現單點登入SSO(Single Sign-On)。

  我們知道,使用者的即時狀態通常是使用Application、Session、Cookie和儲存的。而這些都是不能在程式中跨網站訪問的。我們必需通過網站間相互連訊來確認使用者的即時狀態。

   簡單的實現

  如圖所示,該圖描述了使用者訪問System的流程。


  第一步,假設使用者訪問了Shop或Office的任何一個頁面Any。該頁面所在的網站將會檢查使用者的即時狀態。如果使用者已經登入了,則將Any頁面的資訊返回給使用者。如果使用者還沒有登入,則自動轉到Service的Validate頁面,驗證使用者在Service狀態。即Shop或Office向Service發出請求,要求Service返回使用者的即時狀態。

  第二步,Validate驗證使用者的即時狀態,如果使用者已經登入了,則Service將使用者的即時狀態返回給Shop或Office的同步頁面Synchronous,通知Shop或Office同步處理的使用者狀態。如果使用者沒有登入,則自動轉向Customer頁面,提示使用者登入。

  第三步,使用者完成登入過程,當使用者成功登入後,自動轉回Validate頁面,通知Shop或Office的Synchronous進行使用者狀態的同步。

  第四步,在使用者狀態同步完成後,在本地網站,使用者狀態成為線上狀態,即可訪問Any頁面。

  在上面的流程中。我們知道,不管使用者訪問哪個網站,使用者只需要一次登入,就保證使用者在Service的即時狀態都是線上的,不會再需要進行第二次登入的過程。

  現在我們的思路已經清楚,具體的實現我們將在程式碼分析中完成。

  程式碼分析

  從上面的流程中我們可以看出,系統中Shop和Office的代碼是完全類似的。只要Shop可以實現,Office也可以同樣的複製。所以我們的重點分析的對象是Shop和Service的代碼。

  1、Shop的Web.config和Project.cs

  在Shop的Web.config裡,我們配置了Service網站和Shop網站,以方便我們在部署時方便修改。

<appsettings>
<add key="Service" value="http://localhost:8001" />
<add key="WebSite" value="http://localhost:8002" />
</appsettings>


  在Project類裡進行引用。

using System;
using System.Configuration;

namespace Amethysture.SSO.Shop
{
 public class Project
 {
  public static string Service = ConfigurationSettings.AppSettings["Service"];
  public static string WebSite = ConfigurationSettings.AppSettings["WebSite"];
 }
}


  2、Shop的Global.cs

  Shop的Global.cs定義了四個Session變數,UserID用來標識使用者身份。Pass標識使用者即時狀態,Security用於儲存往來Service和Shop的通訊不是被仿冒的。Url儲存了上次請求的頁面,以保證在使用者登入後能轉到使用者請求的頁面。

protected void Session_Start(Object sender, EventArgs e)
{
 this.Session.Add("UserID", 0);
 this.Session.Add("Pass", false);
 this.Session.Add("Security", "");
 this.Session.Add("Url", "");
}


  3、Shop的Any.cs

  Shop的Any.cs並沒有包含代碼,因為Any類從Page繼承而來,為了程式碼分析方便,我們將代碼集中到Page.cs中。

using System;
using System.Web;

namespace Amethysture.SSO.Shop
{
 public class Any : Amethysture.SSO.Shop.Page
 {
 }
}


  4、Shop的Page.cs

  Page類有兩個方法,CustomerValidate和Initialize。CustomerValidate使用者檢查使用者的即時狀態,而Initialize是頁面登入後發送給使用者的資訊。我們的重點是CustomerValidate。

  CustomerValidate是一個非常簡單的流程,用條件陳述式檢查Pass的狀態,如果Pass為否,則表示使用者沒有登入,頁面跳轉到Service的Validate頁面中。我們要分析的是其中儲存的Url和遞交的WebSite和Security幾個參數。Url的作用在前面已經講清楚了,只是為了保證使用者登入後能回到原來的頁面。而WebSite是為了保證該網站是被Service所接受的,並且保證Service知道是哪個網站請求了使用者即時狀態。因為這個例子是個簡單的例子,在後面的Validate裡沒有驗證WebSite是否是接受的請求網站,但是在實際應用中應該驗證這一點,因為Shop和Service等同於伺服器和用戶端,伺服器出於安全考慮必須要檢查用戶端是否是被允許的。Security是非常重要的一點。Shop對Service發送的是請求,不需要保證該請求沒有被篡改,但是在Service應答Shop請求時就必須要保證應答的資料沒有被篡改了。Security正是為了保證資料安全而設計的。

  在代碼中,Security是通過Hash一個隨機產生的數字產生的。具有不確定性。和保密性。我們可以看到,Security同時儲存在Session中和發送給Service。我們把這個Security當作明文。在後面我們可以看到,Security在Service經過再一次Hash後作為密文發送回Shop。如果我們將Session儲存的Security經過同樣的Hash方法處理後等到的字串如果和Service返回的密文相同,我們就能夠在一定程度上保證Service應答的資料是沒有經過修改的。

using System;
using System.Web;
using System.Security.Cryptography;
using System.Text;

namespace Amethysture.SSO.Shop
{
 public class Page : System.Web.UI.Page
 {
  private void CustomerValidate()
  {
   bool Pass = (bool) this.Session["Pass"];
   if (!Pass)
   {
    string Security = "";
    Random Seed = new Random();
    Security = Seed.Next(1, int.MaxValue).ToString();
    byte[] Value;
    UnicodeEncoding Code = new UnicodeEncoding();
    byte[] Message = Code.GetBytes(Security);
    SHA512Managed Arithmetic = new SHA512Managed();
    Value = Arithmetic.ComputeHash(Message);
    Security = "";
    foreach(byte o in Value)
    {
     Security += (int) o + "O";
    }
    this.Session["Security"] = Security;
    this.Session["Url"] = this.Request.RawUrl;
    this.Response.Redirect(Project.Service + "/Validate.aspx?WebSite=" + Project.WebSite + "&Security=" + Security);
   }
  }

  protected virtual void Initialize()
  {
   this.Response.Write("<html>");
   this.Response.Write("<head>");
   this.Response.Write("<title>Amethysture SSO Project</title>");
   this.Response.Write("<link rel=stylesheet type=\"text/css\" href=\"" + project.website + "/Default.css\">");
   this.Response.Write("</head>");
   this.Response.Write("<body>");
   this.Response.Write("<iframe width=\"0\" height=\"0\" src=\"" + project.service + "/Customer.aspx\"></iframe>");
   this.Response.Write("<div align=\"center\">");
   this.Response.Write("Amethysture SSO Shop Any Page");
   this.Response.Write("</div>");
   this.Response.Write("</body>");
   this.Response.Write("</html>");
  }

  protected override void OnInit(EventArgs e)
  {
   base.OnInit(e);
   this.CustomerValidate();
   this.Initialize();
   this.Response.End();
  }
 }
}


  5、Service的Global.cs

  現在我們頁面轉到了Service的Validate頁面,我們轉過來看Service的代碼。在Global中我們同樣定義了四個Session變數,都和Shop的Session用處類似。WebSite是儲存請求使用者即時狀態的網站資訊。以便能在登入後返回正確的請求網站。

protected void Session_Start(Object sender, EventArgs e)
{
 this.Session.Add("UserID", 0);
 this.Session.Add("Pass", false);
 this.Session.Add("WebSite", "");
 this.Session.Add("Security", "");
}


  6、Service的Validate.cs

  首先,將Shop傳遞過來的參數儲存到Session中。如果使用者沒有登入,則轉到Customer頁面進行登入。如果使用者已經登入了。則將使用者即時狀態傳回給Shop網站。如上所述,這裡將Security重新Hash了一次傳回給Shop,以保證資料不被纂改。

private void CustomerValidate()
{
 bool Pass = (bool) this.Session["Pass"];
 if ((this.Request.QueryString["WebSite"] != null) && (this.Request.QueryString["WebSite"] != ""))
 {
  this.Session["WebSite"] = this.Request.QueryString["WebSite"];
 }
 if ((this.Request.QueryString["Security"] != null) && (this.Request.QueryString["Security"] != ""))
 {
  this.Session["Security"] = this.Request.QueryString["Security"];
 }
 if (Pass)
 {
  string UserID = this.Session["UserID"].ToString();
  string WebSite = this.Session["WebSite"].ToString();
  string Security = this.Session["Security"].ToString();
  byte[] Value;
  UnicodeEncoding Code = new UnicodeEncoding();
  byte[] Message = Code.GetBytes(Security);
  SHA512Managed Arithmetic = new SHA512Managed();
  Value = Arithmetic.ComputeHash(Message);
  Security = "";
  foreach(byte o in Value)
  {
   Security += (int) o + "O";
  }
  this.Response.Redirect(WebSite + "/Synchronous.aspx?UserID=" + UserID + "&Pass=True&Security=" + Security);
 }
 else
 {
  this.Response.Redirect("Customer.aspx");
 }
}


  7、Service的Customer.cs和Login.cs

  Customer主要的是一個用於登入的表單,這裡就不貼出代碼了。這裡分析一下Login的一段代碼,這段代碼是當登入是直接在Service完成的(WebSite為空白值),則頁面不會轉到Shop或Office網站。所以應該暫停在Service網站。系統如果比較完美,這裡應該顯示一組字系統的轉向連結。下面我們看到,當Pass為真時,頁面轉回到Validate頁面,通過上面的分析,我們知道,頁面會轉向Shop的Synchronous頁面,進行使用者狀態的同步。

if (Pass)
{
 if ((this.Session["WebSite"].ToString() != "") && (this.Session["Security"].ToString() != ""))
 {
  this.Response.Redirect("Validate.aspx");
 }
 else
 {
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("Pass");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
 }
}
else
{
 this.Response.Redirect("Customer.aspx");
}


  8、Shop的Synchronous.cs

  好了,我們在Service中完成了登入,並把使用者狀態傳遞迴Shop網站。我們接著看使用者狀態是怎麼同步的。首先,如果Session裡的Security是Null 字元串,則表示Shop網站沒有向Service發送過請求,而Service向Shop發回了請求,這顯然是錯誤的。這次訪問是由用戶端偽造進行的訪問,於是訪問被拒絕了。同樣Security和InSecurity不相同,則表示請求和應答是不匹配的。可能應答被纂改過了,所以應答同樣被拒絕了。當檢驗Security通過後,我們保證Serive完成了應答,並且返回了確切的參數,下面就是讀出參數同步Shop網站和Service網站的使用者即時狀態。

string InUserID = this.Request.QueryString["UserID"];
string InPass = this.Request.QueryString["Pass"];
string InSecurity = this.Request.QueryString["Security"];

string Security = this.Session["Security"].ToString();
if (Security != "")
{
 byte[] Value;
 UnicodeEncoding Code = new UnicodeEncoding();
 byte[] Message = Code.GetBytes(Security);
 SHA512Managed Arithmetic = new SHA512Managed();
 Value = Arithmetic.ComputeHash(Message);
 Security = "";
 foreach(byte o in Value)
 {
  Security += (int) o + "O";
 }

 if (Security == InSecurity)
 {
  if (InPass == "True")
  {
   this.Session["UserID"] = int.Parse(InUserID);
   this.Session["Pass"] = true;
   this.Response.Redirect(this.Session["Url"].ToString());
  }
 }
 else
 {
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("資料錯誤");
  this.Response.Write("");
  this.Response.Write("");
  this.Response.Write("");
 }
}
else
{
 this.Response.Write("");
 this.Response.Write("");
 this.Response.Write("");
 this.Response.Write("");
 this.Response.Write("");
 this.Response.Write("");
 this.Response.Write("");
 this.Response.Write("訪問錯誤");
 this.Response.Write("");
 this.Response.Write("");
 this.Response.Write("");
}


  9、Shop的Page.cs

  我們知道,頁面在一段時間不重新整理後,Session會逾時失效,在我們一直訪問Shop的時候怎麼才能保證Service的Session不會失效呢?很簡單,我們返回來看Shop的Page.cs。通過在所有Shop的頁面內都用<iframe>嵌套Service的某個頁面,就能保證Service能和Shop的頁面同時重新整理。需要注意的一點是Service的Session必須保證不小於所有Shop和Office的Session逾時時間。這個在Web.config裡可以進行配置。

this.Response.Write("<iframe width=\"0\" height=\"0\" src=\"" + project.service + "/Customer.aspx\"></iframe>");


   總結

  一次完整的登入完成了。我們接著假設一下現在要跳到Office的Any頁面,系統會進行怎樣的操作呢?Any(使用者沒有登入)->Validate(使用者已經登入)->Synchronous(同步)->Any。也就是說這次,使用者沒有進行登入的過程。我們通過一次登入,使得Service的使用者狀態為登入,並且不管有多少個網站應用程式,只要這些網站都保證符合Shop的特性,這些網站就都能保持Service的使用者狀態,同時能通過Service獲得使用者的狀態。也就是說我們實現了SSO。

相關文章

E-Commerce Solutions

Leverage the same tools powering the Alibaba Ecosystem

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。