欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.NET单点登陆的实现方法及思路

程序员文章站 2024-02-16 13:00:22
系统的基本架构    我们假设一个系统system包含service客户服务中心、shop网上购物中心和office网上办公中心三个独立的网站。 service管理客户的资...
系统的基本架构
   我们假设一个系统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站点,以方便我们在部署时方便修改。
复制代码 代码如下:

<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是空字符串,则表示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