.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
我们假设一个系统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