E-Mail:115294781@qq.com 联系电话: 020-84226565

单点登陆.NET的简单实现 (转)

2013-10-24

系统的基本架构

   我们假设一个系统System包含Service客户服务中心、Shop网上购物中心和Office网上办公中心三个独立的网站。 Service管理客户的资料,登录和注销过程。不论客户访问System的任何一个页面,系统都会转到登录界面,在用户登录后,系统会自动转会到客户上 次请求的页面。并且用户此后可以在System中无缝切换。不需要再次进行登录。即在System中实现单点登录SSO(Single Sign-On)。

  我们知道,用户的即时状态通常是使用Application、Session、Cookie和存储的。而这些都是不能在程序中跨站点访问的。我们必需通过站点间相互通讯来确认用户的即时状态。

 

 简单的实现

 

   第一步,假设用户访问了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站点,以方便我们在部署时方便修改。


 

  1. <appsettings>
  2. <add key="Service" value="http://localhost:8001" />
  3. <add key="WebSite" value="http://localhost:8002" />
  4. </appsettings> 

  5.  

  在Project类里进行引用。


 

  1. using System;
  2. using System.Configuration;

  3.  
  4. namespace Amethysture.SSO.Shop
  5. {
  6.  public class Project
  7.  {
  8.   public static string Service = ConfigurationSettings.AppSettings["Service"];
  9.   public static string WebSite = ConfigurationSettings.AppSettings["WebSite"];
  10.  }
  11. }

  2、Shop的Global.cs

   Shop的Global.cs定义了四个Session变量,UserID用来标识用 户身份。Pass标识用户即时状态,Security用于保存往来Service和Shop的通讯不是被仿冒的。Url保存了上次请求的页面,以保证在用 户登录后能转到用户请求的页面。


 

  1. protected void Session_Start(Object sender, EventArgs e)
  2. {
  3.  this.Session.Add("UserID", 0);
  4.  this.Session.Add("Pass", false);
  5.  this.Session.Add("Security", "");
  6.  this.Session.Add("Url", "");
  7. }

  3、Shop的Any.cs

  Shop的Any.cs并没有包含代码,因为Any类从Page继承而来,为了代码分析方便,我们将代码集中到Page.cs中。

 

  1. using System;
  2. using System.Web;

  3.  
  4. namespace Amethysture.SSO.Shop
  5. {
  6.  public class Any : Amethysture.SSO.Shop.Page
  7.  {
  8.  }
  9. }

  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应答的数据是没有经过修改的。

 

  1. using System;
  2. using System.Web;
  3. using System.Security.Cryptography;
  4. using System.Text;

  5.  
  6. namespace Amethysture.SSO.Shop
  7. {
  8.  public class Page : System.Web.UI.Page
  9.  {
  10.   private void CustomerValidate()
  11.   {
  12.    bool Pass = (bool) this.Session["Pass"];
  13.    if (!Pass)
  14.    {
  15.     string Security = "";
  16.     Random Seed = new Random();
  17.     Security = Seed.Next(1, int.MaxValue).ToString();
  18.     byte[] Value;
  19.     UnicodeEncoding Code = new UnicodeEncoding();
  20.     byte[] Message = Code.GetBytes(Security);
  21.     SHA512Managed Arithmetic = new SHA512Managed();
  22.     Value = Arithmetic.ComputeHash(Message);
  23.     Security = "";
  24.     foreach(byte o in Value)
  25.     {
  26.      Security += (int) o + "O";
  27.     }
  28.     this.Session["Security"] = Security;
  29.     this.Session["Url"] = this.Request.RawUrl;
  30.     this.Response.Redirect(Project.Service + "/Validate.aspx?WebSite=" + Project.WebSite + "&Security=" + Security);
  31.    }
  32.   }

  33.  
  34.   protected virtual void Initialize()
  35.   {
  36.    this.Response.Write("<html>");
  37.    this.Response.Write("<head>"); 
  38.    this.Response.Write("<title>Amethysture SSO Project</title>");
  39.    this.Response.Write("<link rel=stylesheet type=\"text/css\" href=\"" + project.website + "/Default.css\">");
  40.    this.Response.Write("</head>");
  41.    this.Response.Write("<body>");
  42.    this.Response.Write("<iframe width=\"0\" height=\"0\" src=\"" + project.service + "/Customer.aspx\"></iframe>");
  43.    this.Response.Write("<div align=\"center\">");
  44.    this.Response.Write("Amethysture SSO Shop Any Page");
  45.    this.Response.Write("</div>");
  46.    this.Response.Write("</body>");
  47.    this.Response.Write("</html>");
  48.   }

  49.  
  50.   protected override void OnInit(EventArgs e)
  51.   {
  52.    base.OnInit(e);
  53.    this.CustomerValidate();
  54.    this.Initialize();
  55.    this.Response.End();
  56.   }
  57.  }
  58. }

5、Service的Global.cs

   现在我们页面转到了Service的Validate页面,我们转过来看 Service的代码。在Global中我们同样定义了四个Session变量,都和Shop的Session用处类似。WebSite是保存请求用户即 时状态的站点信息。以便能在登录后返回正确的请求站点。
 


 

  1. protected void Session_Start(Object sender, EventArgs e)
  2. {
  3.  this.Session.Add("UserID", 0);
  4.  this.Session.Add("Pass", false);
  5.  this.Session.Add("WebSite", "");
  6.  this.Session.Add("Security", "");
  7. }

  6、Service的Validate.cs

  首先,将Shop传递过来的参数保存到Session中。如果用户没有登录,则转到Customer页面进行登录。如果用户已经登录了。则将用户即时状态传回给Shop站点。如上所述,这里将Security重新Hash了一次传回给Shop,以保证数据不被纂改。

 

 


 

  1. private void CustomerValidate()
  2. {
  3.  bool Pass = (bool) this.Session["Pass"];
  4.  if ((this.Request.QueryString["WebSite"] != null) && (this.Request.QueryString["WebSite"] != ""))
  5.  {
  6.   this.Session["WebSite"] = this.Request.QueryString["WebSite"];
  7.  }
  8.  if ((this.Request.QueryString["Security"] != null) && (this.Request.QueryString["Security"] != ""))
  9.  {
  10.   this.Session["Security"] = this.Request.QueryString["Security"];
  11.  }
  12.  if (Pass)
  13.  {
  14.   string UserID = this.Session["UserID"].ToString();
  15.   string WebSite = this.Session["WebSite"].ToString();
  16.   string Security = this.Session["Security"].ToString();
  17.   byte[] Value;
  18.   UnicodeEncoding Code = new UnicodeEncoding();
  19.   byte[] Message = Code.GetBytes(Security);
  20.   SHA512Managed Arithmetic = new SHA512Managed();
  21.   Value = Arithmetic.ComputeHash(Message);
  22.   Security = "";
  23.   foreach(byte o in Value)
  24.   {
  25.    Security += (int) o + "O";
  26.   }
  27.   this.Response.Redirect(WebSite + "/Synchronous.aspx?UserID=" + UserID + "&Pass=True&Security=" + Security);
  28.  }
  29.  else
  30.  {
  31.   this.Response.Redirect("Customer.aspx");
  32.  }
  33. }

  7、Service的Customer.cs和Login.cs

   Customer主要的是一个用于登录的表单,这里就不 贴出代码了。这里分析一下Login的一段代码,这段代码是当登录是直接在Service完成的(WebSite为空值),则页面不会转到Shop或 Office站点。所以应该暂停在Service站点。系统如果比较完美,这里应该显示一组字系统的转向链接。下面我们看到,当Pass为真时,页面转回 到Validate页面,通过上面的分析,我们知道,页面会转向Shop的Synchronous页面,进行用户状态的同步。

 

 


 

  1. if (Pass)
  2. {
  3.  if ((this.Session["WebSite"].ToString() != "") && (this.Session["Security"].ToString() != ""))
  4.  {
  5.   this.Response.Redirect("Validate.aspx");
  6.  }
  7.  else
  8.  {
  9.   this.Response.Write("");
  10.   this.Response.Write(""); 
  11.   this.Response.Write("");
  12.   this.Response.Write("");
  13.   this.Response.Write("");
  14.   this.Response.Write("");
  15.   this.Response.Write("");
  16.   this.Response.Write("Pass");
  17.   this.Response.Write("");
  18.   this.Response.Write("");  
  19.   this.Response.Write("");
  20.  }
  21. }
  22. else
  23. {
  24.  this.Response.Redirect("Customer.aspx");
  25. }

  8、Shop的Synchronous.cs

   好了,我们在Service中完成了登录,并把用户状态传递回Shop站 点。我们接着看用户状态是怎么同步的。首先,如果Session里的Security是空字符串,则表示Shop站点没有向Service发送过请求,而 Service向Shop发回了请求,这显然是错误的。这次访问是由客户端伪造进行的访问,于是访问被拒绝了。同样Security和 InSecurity不相同,则表示请求和应答是不匹配的。可能应答被纂改过了,所以应答同样被拒绝了。当检验Security通过后,我们保证 Serive完成了应答,并且返回了确切的参数,下面就是读出参数同步Shop站点和Service站点的用户即时状态。

 

 


 

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

  4.  
  5. string Security = this.Session["Security"].ToString();
  6. if (Security != "")
  7. {
  8.  byte[] Value;
  9.  UnicodeEncoding Code = new UnicodeEncoding();
  10.  byte[] Message = Code.GetBytes(Security);
  11.  SHA512Managed Arithmetic = new SHA512Managed();
  12.  Value = Arithmetic.ComputeHash(Message);
  13.  Security = "";
  14.  foreach(byte o in Value)
  15.  {
  16.   Security += (int) o + "O";
  17.  }

  18.  
  19.  if (Security == InSecurity)
  20.  {
  21.   if (InPass == "True")
  22.   {
  23.    this.Session["UserID"] = int.Parse(InUserID);
  24.    this.Session["Pass"] = true;
  25.    this.Response.Redirect(this.Session["Url"].ToString());
  26.   }
  27.  }
  28.  else
  29.  {
  30.   this.Response.Write("");
  31.   this.Response.Write(""); 
  32.   this.Response.Write("");
  33.   this.Response.Write("");
  34.   this.Response.Write("");
  35.   this.Response.Write("");
  36.   this.Response.Write("");
  37.   this.Response.Write("数据错误");
  38.   this.Response.Write("");
  39.   this.Response.Write("");
  40.   this.Response.Write("");
  41.  }
  42. }
  43. else
  44. {
  45.  this.Response.Write("");
  46.  this.Response.Write(""); 
  47.  this.Response.Write("");
  48.  this.Response.Write("");
  49.  this.Response.Write("");
  50.  this.Response.Write("");
  51.  this.Response.Write("");
  52.  this.Response.Write("访问错误");
  53.  this.Response.Write("");
  54.  this.Response.Write("");
  55.  this.Response.Write("");
  56. }

  9、Shop的Page.cs

   我们知道,页面在一段时间不刷新后,Session会超时失效,在我们一直访问Shop的 时候怎么才能保证Service的Session不会失效呢?很简单,我们返回来看Shop的Page.cs。通过在所有Shop的页面内都用 <iframe>嵌套Service的某个页面,就能保证Service能和Shop的页面同时刷新。需要注意的一点是Service的Session必 须保证不小于所有Shop和Office的Session超时时间。这个在Web.config里可以进行配置。

 

 


 

  1. 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。