asp.net 真正实现完全跨域单点登录

单点登录Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

 

asp.net跨域单点登录分为:

1、跨子域单点登录。如 blog.a.com 和 info.a.com 这2个站点同属一个主域.a.com,实现跨子域单点登录很简单,可以利用cookie,设置Domain为".a.com'即可,这里就不再赘叙。

2、完成跨域单点登录。如 http://www.a.com/   http://www.b.com/ 这2个站点之间实现共享一个身份验证系统,只需在一处地方登录,下面主要谈下这种方式的实现方法。 

 

asp.net 跨域单点登录实现原理:

当用户第一次访问web应用系统1的时候,因为还没有登录,会被引导到认证中心进行登录;根据用户提供的登录信息,认证系统进行身份效验,如果
通过效验,返回给用户一个认证的凭据;用户再访问别的web应用的时候就会将这个Token带上,作为自己认证的凭据,应用系统接受到请求之后会把Token送到认证中心进行效验,检查Token的合法性。如果通过效验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。所有应用系统共享一个身份认证系统。认证系统的主要功能是将用户的登录信息和用户信息库相比较,对用户进行登录认证;认证成功后,认证系统应该生成统一的认证标志,返还给用户。另外,认证系统还应该对Token进行效验,判断其有效性。 所有应用系统能够识别和提取Token信息要实现SSO的功能,让用户只登录一次,就必须让应用系统能够识别已经登录过的用户。应用系统应该能对Token进行识别和提取,通过与认证系统的通讯,能自动判断当前用户是否登录过,从而完成单点登录的功能。

 

比如说,我现在有3个分站点和1个认证中心(总站)。当用户访问分站点的时候,分站点会发Token到验证中心进行验证。验证中心判断用户是否已经登录。如果未登录,则返回到验证中心登录入口进行登录,否之则返回Token验证到分站点,直接进入分站点。


如图所示:

 

单点登录流程图

 

 

 

 

上面是实现单点登录的原理图,下面介绍下如何用asp.net实现跨域单点登录:

 

一、新建网站 MasterSite,作为总站认证中心。配置web.config,采用form登录验证。
      配置如下:

     

  1. <authentication mode="Forms">  
  2.   <forms name=".AspxFormAuth" loginUrl="Default.aspx" defaultUrl="center.html" protection="All" path="/" timeout="120">  
  3.   </forms>  
  4. </authentication>  
  5. <authorization>  
  6.     <!--拒绝所有匿名用户-->  
  7.     <deny users="?"/>  
  8. </authorization>  

 

 

      添加Default.aspx页面,用来进行登录。代码如下:
   

     HTML Code:

    

  1. <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>  
  2.   
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  4.   
  5. <html xmlns="http://www.w3.org/1999/xhtml" >  
  6. <head runat="server">  
  7.     <title>总站登录</title>  
  8. </head>  
  9. <body>  
  10.     <form id="form1" runat="server">  
  11.     <div>  
  12.         <asp:Login ID="Login1" runat="server" OnAuthenticate="Login1_Authenticate" UserName="test">  
  13.         </asp:Login>  
  14.     </div>  
  15.     </form>  
  16. </body>  
  17. </html>  

 


   Default.cs Code:

  

  1. using System;  
  2. using System.Data;  
  3. using System.Configuration;  
  4. using System.Web;  
  5. using System.Web.Security;  
  6. using System.Web.UI;  
  7. using System.Web.UI.WebControls;  
  8. using System.Web.UI.WebControls.WebParts;  
  9. using System.Web.UI.HtmlControls;  
  10. using System.Text;  
  11.   
  12. public partial class _Default : System.Web.UI.Page   
  13. {  
  14.     protected void Page_Load(object sender, EventArgs e)  
  15.     {  
  16.         if (!IsPostBack)  
  17.         {  
  18.             SSORequest ssoRequest = new SSORequest();  
  19.  
  20.  
  21.             #region 验证 Post 过来的参数  
  22.             //--------------------------------  
  23.             // 请求注销  
  24.             if (!string.IsNullOrEmpty(Request["Logout"]))  
  25.             {  
  26.                 Authentication.Logout();  
  27.                 return;  
  28.             }  
  29.             //--------------------------------  
  30.             // 各独立站点标识  
  31.             if (string.IsNullOrEmpty(Request["IASID"]))  
  32.             {  
  33.                 return;  
  34.             }  
  35.             else  
  36.             {  
  37.                 ssoRequest.IASID = Request["IASID"];  
  38.             }  
  39.   
  40.             //--------------------------------  
  41.             // 时间戳  
  42.             if (string.IsNullOrEmpty(Request["TimeStamp"]))  
  43.             {  
  44.                 return;  
  45.             }  
  46.             else  
  47.             {  
  48.                 ssoRequest.TimeStamp = Request["TimeStamp"];  
  49.             }  
  50.   
  51.             //--------------------------------  
  52.             // 各独立站点的访问地址  
  53.             if (string.IsNullOrEmpty(Request["AppUrl"]))  
  54.             {  
  55.                 return;  
  56.             }  
  57.             else  
  58.             {  
  59.                 ssoRequest.AppUrl = Request["AppUrl"];  
  60.             }  
  61.   
  62.             //--------------------------------  
  63.             // 各独立站点的 Token  
  64.             if (string.IsNullOrEmpty(Request["Authenticator"]))  
  65.             {  
  66.                 return;  
  67.             }  
  68.             else  
  69.             {  
  70.                 ssoRequest.Authenticator = Request["Authenticator"];  
  71.             }  
  72.   
  73.             ViewState["SSORequest"] = ssoRequest;  
  74.  
  75.             #endregion  
  76.   
  77.   
  78.             //验证从分站发过来的Token  
  79.             if (Authentication.ValidateAppToken(ssoRequest))  
  80.             {  
  81.                 string userAccount = null;  
  82.   
  83.                 // 验证用户之前是否登录过  
  84.                 //验证 EAC 认证中心的 Cookie,验证通过时获取用户登录账号  
  85.                 if (Authentication.ValidateEACCookie(out userAccount))  
  86.                 {  
  87.                     ssoRequest.UserAccount = userAccount;  
  88.   
  89.                     //创建认证中心发往各分站的 Token  
  90.                     if (Authentication.CreateEACToken(ssoRequest))  
  91.                     {  
  92.                         Post(ssoRequest);  
  93.                     }  
  94.                 }  
  95.                 else  
  96.                 {  
  97.                     return;  
  98.                 }  
  99.             }  
  100.             else  
  101.             {  
  102.                 return;  
  103.             }  
  104.         }  
  105.     }  
  106.   
  107.   
  108.     //post请求  
  109.     void Post(SSORequest ssoRequest)  
  110.     {  
  111.         PostService ps = new PostService();  
  112.   
  113.         ps.Url = ssoRequest.AppUrl;  
  114.   
  115.         ps.Add("UserAccount", ssoRequest.UserAccount);  
  116.         ps.Add("IASID", ssoRequest.IASID);  
  117.         ps.Add("TimeStamp", ssoRequest.TimeStamp);  
  118.         ps.Add("AppUrl", ssoRequest.AppUrl);  
  119.         ps.Add("Authenticator", ssoRequest.Authenticator);  
  120.   
  121.         ps.Post();  
  122.     }  
  123.   
  124.     /// <summary>  
  125.     /// 验证登录账号和密码是否正确  
  126.     /// </summary>  
  127.     /// <param name="userName">登录账号</param>  
  128.     /// <param name="userPwd">登录密码</param>  
  129.     /// <returns></returns>  
  130.     private bool ValidateUserInfo(string userName, string userPwd)  
  131.     {  
  132.         //从数据库中读取,验证登录账号和密码  
  133.         //略...  
  134.         return true;  
  135.     }  
  136.   
  137.     protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)  
  138.     {  
  139.         if (string.IsNullOrEmpty(Login1.UserName) || string.IsNullOrEmpty(Login1.Password))  
  140.         {  
  141.             Page.RegisterClientScriptBlock("Add""<mce:script lanuage=/"javascript/"><!--  
  142. alert('用户名密码不能为空!');  
  143. // --></mce:script>");  
  144.             return;  
  145.         }  
  146.         else if (ValidateUserInfo(Login1.UserName, Login1.Password) == false)  
  147.         {  
  148.             Page.RegisterClientScriptBlock("Add""<mce:script lanuage=/"javascript/"><!--  
  149. alert('用户名密码错误!');  
  150. // --></mce:script>");  
  151.             return;  
  152.         }  
  153.         else  
  154.         {  
  155.             Session["CurrUserName"] = Login1.UserName;  
  156.             Session.Timeout = 120;  
  157.   
  158.             SSORequest ssoRequest = ViewState["SSORequest"as SSORequest;  
  159.   
  160.             // 如果不是从各分站 Post 过来的请求,则默认登录主站  
  161.             if (ssoRequest == null)  
  162.             {  
  163.                 FormsAuthentication.SetAuthCookie(Login1.UserName, false);  
  164.   
  165.                 ssoRequest = new SSORequest();  
  166.                 //主站标识ID  
  167.                 ssoRequest.IASID = "00";  
  168.                 ssoRequest.AppUrl = "SiteList.aspx";  
  169.                 ssoRequest.TimeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm");  
  170.                 ssoRequest.Authenticator = string.Empty;  
  171.   
  172.                 Response.Redirect("SiteList.aspx");  
  173.             }  
  174.             ssoRequest.UserAccount = Login1.UserName;  
  175.   
  176.             //创建Token  
  177.             if (Authentication.CreateEACToken(ssoRequest))  
  178.             {  
  179.                 string expireTime = DateTime.Now.AddHours(3).ToString("yyyy-MM-dd HH:mm");  
  180.   
  181.                 Authentication.CreatEACCookie(ssoRequest.UserAccount, ssoRequest.TimeStamp, expireTime);  
  182.   
  183.                 Post(ssoRequest);  
  184.             }  
  185.   
  186.         }  
  187.     }  
  188.   
  189.       
  190. }  

 

  代码说明:验证分站post过来的Token请求,如果用户已经登录,则创建认证中心发往各分站的 Token验证,转向分站,否之则返回登录。若是直接登录主站则转向站点选择页面sitelist.aspx,选择你要登录的分站点。

如图:

主站登录

 

 

选择站点

 

 

 

二、新建站点1,代码如下:

 

HTML Code:

 

  1. <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>  
  2.   
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  4.   
  5. <html xmlns="http://www.w3.org/1999/xhtml" >  
  6. <head runat="server">  
  7.     <title> 站点一</title>  
  8. </head>  
  9. <body>  
  10.     <form id="form1" runat="server">  
  11.     <div>  
  12.         <br />  
  13.         <br />  
  14.         <asp:LinkButton ID="LinkButton1" runat="server" OnClick="LinkButton1_Click">返回主站</asp:LinkButton>  
  15.             
  16.         <asp:LinkButton ID="LinkButton2" runat="server" OnClick="LinkButton2_Click">注销登录</asp:LinkButton></div>  
  17.     </form>  
  18. </body>  
  19. </html>  

 

 

 

Default.cs code:

 

  1. using System;  
  2. using System.Data;  
  3. using System.Configuration;  
  4. using System.Web;  
  5. using System.Web.Security;  
  6. using System.Web.UI;  
  7. using System.Web.UI.WebControls;  
  8. using System.Web.UI.WebControls.WebParts;  
  9. using System.Web.UI.HtmlControls;  
  10. using System.Text;  
  11.   
  12. public partial class _Default : System.Web.UI.Page   
  13. {  
  14.     protected void Page_Load(object sender, EventArgs e)  
  15.     {  
  16.         if (!IsPostBack)  
  17.         {  
  18.             #region SSO 部分代码  
  19.             SSORequest ssoRequest = new SSORequest();  
  20.   
  21.             if (string.IsNullOrEmpty(Request["IASID"]))  
  22.             {  
  23.                 ssoRequest.IASID = "01";  
  24.                 ssoRequest.TimeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm");  
  25.                 ssoRequest.AppUrl = Request.Url.ToString();  
  26.                 Authentication.CreateAppToken(ssoRequest);  
  27.   
  28.                 Post(ssoRequest);  
  29.             }  
  30.             else if (!string.IsNullOrEmpty(Request["IASID"])  
  31.                 && !string.IsNullOrEmpty(Request["TimeStamp"])  
  32.                 && !string.IsNullOrEmpty(Request["AppUrl"])  
  33.                 && !string.IsNullOrEmpty(Request["UserAccount"])  
  34.                 && !string.IsNullOrEmpty(Request["Authenticator"]))  
  35.             {  
  36.                 ssoRequest.IASID = Request["IASID"];  
  37.                 ssoRequest.TimeStamp = Request["TimeStamp"];  
  38.                 ssoRequest.AppUrl = Request["AppUrl"];  
  39.                 ssoRequest.UserAccount = Request["UserAccount"];  
  40.                 ssoRequest.Authenticator = Request["Authenticator"];  
  41.   
  42.                 if (Authentication.ValidateEACToken(ssoRequest))  
  43.                 {  
  44.                     //从数据库中获取UserId  
  45.                     Session["CurrUserName"] = Request["UserAccount"];  
  46.                     Session.Timeout = 120;  
  47.                     FormsAuthentication.SetAuthCookie(Request["UserAccount"], false);  
  48.                     Response.Write(string.Format("{0},您好!欢迎来到site1,  >> 访问<a href="/" mce_href="/""http://localhost/Site2/Default.aspx/">site2</a>",ssoRequest.UserAccount));  
  49.                 }  
  50.             }  
  51.   
  52.             ViewState["SSORequest"] = ssoRequest;  
  53.  
  54.             #endregion  
  55.         }  
  56.     }  
  57.   
  58.     void Post(SSORequest ssoRequest)  
  59.     {  
  60.         PostService ps = new PostService();  
  61.         //认证中心(主站)地址  
  62.         string EACUrl = "http://localhost/MasterSite/Default.aspx";  
  63.         ps.Url = EACUrl;  
  64.         //ps.Add("UserAccount", ssoRequest.UserAccount);  
  65.         ps.Add("IASID", ssoRequest.IASID);  
  66.         ps.Add("TimeStamp", ssoRequest.TimeStamp);  
  67.         ps.Add("AppUrl", ssoRequest.AppUrl);  
  68.         ps.Add("Authenticator", ssoRequest.Authenticator);  
  69.   
  70.         ps.Post();  
  71.     }  
  72.   
  73.   
  74.     //注销登录  
  75.     protected void LinkButton2_Click(object sender, EventArgs e)  
  76.     {  
  77.         FormsAuthentication.SignOut();  
  78.   
  79.         SSORequest ssoRequest = new SSORequest();  
  80.   
  81.         ssoRequest.IASID = "01";  
  82.         ssoRequest.TimeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm");  
  83.         ssoRequest.AppUrl = Request.Url.ToString();  
  84.   
  85.         Authentication.CreateAppToken(ssoRequest);  
  86.   
  87.         PostService ps = new PostService();  
  88.   
  89.         //认证中心(主站)地址  
  90.         string EACUrl = "http://localhost/MasterSite/Default.aspx";  
  91.         ps.Url = EACUrl;  
  92.   
  93.         ps.Add("IASID", ssoRequest.IASID);  
  94.         ps.Add("TimeStamp", ssoRequest.TimeStamp);  
  95.         ps.Add("AppUrl", ssoRequest.AppUrl);  
  96.         ps.Add("Authenticator", ssoRequest.Authenticator);  
  97.   
  98.         ps.Add("Logout""true");  
  99.   
  100.         ps.Post();  
  101.     }  
  102.   
  103.     //返回主站  
  104.     protected void LinkButton1_Click(object sender, EventArgs e)  
  105.     {  
  106.         if (Session["CurrUserName"] != null)  
  107.         {  
  108.             Response.Redirect("http://localhost/MasterSite/SiteList.aspx");  
  109.         }  
  110.     }  
  111. }  

 

 

配置web.config

 

  1. <authentication mode="Forms">  
  2.             <forms name=".AspxFormAuth" loginUrl="Default.aspx" defaultUrl="center.html" protection="All" path="/" timeout="60">  
  3.             </forms>  
  4.         </authentication>  
  5.         <authorization>  
  6.             <!--拒绝所有匿名用户-->  
  7.             <deny users="?"/>  
  8.         </authorization>  

 

 

三、同二一样,新建站点Site2,代码如下:

 

  1. using System;  
  2. using System.Data;  
  3. using System.Configuration;  
  4. using System.Web;  
  5. using System.Web.Security;  
  6. using System.Web.UI;  
  7. using System.Web.UI.WebControls;  
  8. using System.Web.UI.WebControls.WebParts;  
  9. using System.Web.UI.HtmlControls;  
  10.   
  11. public partial class _Default : System.Web.UI.Page  
  12. {  
  13.     protected void Page_Load(object sender, EventArgs e)  
  14.     {  
  15.         if (!IsPostBack)  
  16.         {  
  17.             #region SSO 部分代码  
  18.             SSORequest ssoRequest = new SSORequest();  
  19.   
  20.             if (string.IsNullOrEmpty(Request["IASID"]))  
  21.             {  
  22.                 ssoRequest.IASID = "02";  
  23.                 ssoRequest.TimeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm");  
  24.                 ssoRequest.AppUrl = Request.Url.ToString();  
  25.                 Authentication.CreateAppToken(ssoRequest);  
  26.   
  27.                 Post(ssoRequest);  
  28.             }  
  29.             else if (!string.IsNullOrEmpty(Request["IASID"])  
  30.                 && !string.IsNullOrEmpty(Request["TimeStamp"])  
  31.                 && !string.IsNullOrEmpty(Request["AppUrl"])  
  32.                 && !string.IsNullOrEmpty(Request["UserAccount"])  
  33.                 && !string.IsNullOrEmpty(Request["Authenticator"]))  
  34.             {  
  35.                 ssoRequest.IASID = Request["IASID"];  
  36.                 ssoRequest.TimeStamp = Request["TimeStamp"];  
  37.                 ssoRequest.AppUrl = Request["AppUrl"];  
  38.                 ssoRequest.UserAccount = Request["UserAccount"];  
  39.                 ssoRequest.Authenticator = Request["Authenticator"];  
  40.   
  41.                 if (Authentication.ValidateEACToken(ssoRequest))  
  42.                 {  
  43.                     Session["CurrUserName"] = Request["UserAccount"];  
  44.                     Session.Timeout = 120;  
  45.                     FormsAuthentication.SetAuthCookie(Request["UserAccount"], false);  
  46.                     Response.Write(string.Format("{0},您好!欢迎来到site2,  >> 访问<a href="/" mce_href="/""http://localhost/Site1/Default.aspx/">site1</a>", ssoRequest.UserAccount));  
  47.                 }  
  48.             }  
  49.   
  50.             ViewState["SSORequest"] = ssoRequest;  
  51.  
  52.             #endregion  
  53.         }  
  54.     }  
  55.   
  56.     void Post(SSORequest ssoRequest)  
  57.     {  
  58.         PostService ps = new PostService();  
  59.         //认证中心(主站)地址  
  60.         string EACUrl = "http://localhost/MasterSite/Default.aspx";  
  61.         ps.Url = EACUrl;  
  62.         //ps.Add("UserAccount", ssoRequest.UserAccount);  
  63.         ps.Add("IASID", ssoRequest.IASID);  
  64.         ps.Add("TimeStamp", ssoRequest.TimeStamp);  
  65.         ps.Add("AppUrl", ssoRequest.AppUrl);  
  66.         ps.Add("Authenticator", ssoRequest.Authenticator);  
  67.   
  68.         ps.Post();  
  69.     }  
  70.   
  71.   
  72.     //注销登录  
  73.     protected void LinkButton2_Click(object sender, EventArgs e)  
  74.     {  
  75.         FormsAuthentication.SignOut();  
  76.   
  77.         SSORequest ssoRequest = new SSORequest();  
  78.   
  79.         ssoRequest.IASID = "02";  
  80.         ssoRequest.TimeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm");  
  81.         ssoRequest.AppUrl = Request.Url.ToString();  
  82.   
  83.         Authentication.CreateAppToken(ssoRequest);  
  84.   
  85.         PostService ps = new PostService();  
  86.   
  87.         //认证中心(主站)地址  
  88.         string EACUrl = "http://localhost/MasterSite/Default.aspx";  
  89.         ps.Url = EACUrl;  
  90.   
  91.         ps.Add("IASID", ssoRequest.IASID);  
  92.         ps.Add("TimeStamp", ssoRequest.TimeStamp);  
  93.         ps.Add("AppUrl", ssoRequest.AppUrl);  
  94.         ps.Add("Authenticator", ssoRequest.Authenticator);  
  95.   
  96.         ps.Add("Logout""true");  
  97.   
  98.         ps.Post();  
  99.     }  
  100.   
  101.     //返回主站  
  102.     protected void LinkButton1_Click(object sender, EventArgs e)  
  103.     {  
  104.         if (Session["CurrUserName"] != null)  
  105.         {  
  106.             Response.Redirect("http://localhost/MasterSite/SiteList.aspx");  
  107.         }  
  108.     }  
  109. }  

 

 

 

对于tokent请求,tokent验证,需要对它进行加密、解密。

其它代码:

Authentication.cs

  1. using System;  
  2. using System.Data;  
  3. using System.Configuration;  
  4. using System.Web;  
  5. using System.Web.Security;  
  6. using System.Collections.Generic;  
  7. using System.Text;  
  8.   
  9. /// <summary>  
  10. /// 安全验证类  
  11. /// </summary>  
  12. public class Authentication  
  13. {  
  14.     static readonly string cookieName = "EACToken";  
  15.     static readonly string hashSplitter = "|";  
  16.   
  17.     public Authentication()  
  18.     {  
  19.     }  
  20.   
  21.     public static string GetAppKey(int appID)  
  22.     {  
  23.         //string cmdText = @"select * from ";  
  24.         return string.Empty;  
  25.     }  
  26.   
  27.     public static string GetAppKey()  
  28.     {  
  29.         return "22362E7A9285DD53A0BBC2932F9733C505DC04EDBFE00D70";  
  30.     }  
  31.   
  32.     public static string GetAppIV()  
  33.     {  
  34.         return "1E7FA9231E7FA923";  
  35.     }  
  36.   
  37.     /// <summary>  
  38.     /// 取得加密服务  
  39.     /// </summary>  
  40.     /// <returns></returns>  
  41.     static CryptoService GetCryptoService()  
  42.     {  
  43.         string key = GetAppKey();  
  44.         string IV = GetAppIV();  
  45.   
  46.         CryptoService cs = new CryptoService(key, IV);  
  47.         return cs;  
  48.     }  
  49.   
  50.     /// <summary>  
  51.     /// 创建各分站发往认证中心的 Token  
  52.     /// </summary>  
  53.     /// <param name="ssoRequest"></param>  
  54.     /// <returns></returns>  
  55.     public static bool CreateAppToken(SSORequest ssoRequest)  
  56.     {  
  57.         string OriginalAuthenticator = ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;  
  58.         string AuthenticatorDigest = CryptoHelper.ComputeHashString(OriginalAuthenticator);  
  59.         string sToEncrypt = OriginalAuthenticator + AuthenticatorDigest;  
  60.         byte[] bToEncrypt = CryptoHelper.ConvertStringToByteArray(sToEncrypt);  
  61.   
  62.         CryptoService cs = GetCryptoService();  
  63.   
  64.         byte[] encrypted;  
  65.   
  66.         if (cs.Encrypt(bToEncrypt, out encrypted))  
  67.         {  
  68.             ssoRequest.Authenticator = CryptoHelper.ToBase64String(encrypted);  
  69.   
  70.             return true;  
  71.         }  
  72.         else  
  73.         {  
  74.             return false;  
  75.         }  
  76.     }  
  77.   
  78.   
  79.     /// <summary>  
  80.     /// 验证从各分站发送过来的 Token  
  81.     /// </summary>  
  82.     /// <param name="ssoRequest"></param>  
  83.     /// <returns></returns>  
  84.     public static bool ValidateAppToken(SSORequest ssoRequest)  
  85.     {  
  86.         string Authenticator = ssoRequest.Authenticator;  
  87.   
  88.         string OriginalAuthenticator = ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;  
  89.         string AuthenticatorDigest = CryptoHelper.ComputeHashString(OriginalAuthenticator);  
  90.         string sToEncrypt = OriginalAuthenticator + AuthenticatorDigest;  
  91.         byte[] bToEncrypt = CryptoHelper.ConvertStringToByteArray(sToEncrypt);  
  92.   
  93.         CryptoService cs = GetCryptoService();  
  94.         byte[] encrypted;  
  95.   
  96.         if (cs.Encrypt(bToEncrypt, out encrypted))  
  97.         {  
  98.             return Authenticator == CryptoHelper.ToBase64String(encrypted);  
  99.         }  
  100.         else  
  101.         {  
  102.             return false;  
  103.         }  
  104.     }  
  105.   
  106.   
  107.     /// <summary>  
  108.     /// 创建认证中心发往各分站的 Token  
  109.     /// </summary>  
  110.     /// <param name="ssoRequest"></param>  
  111.     /// <returns></returns>  
  112.     public static bool CreateEACToken(SSORequest ssoRequest)  
  113.     {  
  114.         string OriginalAuthenticator = ssoRequest.UserAccount + ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;  
  115.         string AuthenticatorDigest = CryptoHelper.ComputeHashString(OriginalAuthenticator);  
  116.         string sToEncrypt = OriginalAuthenticator + AuthenticatorDigest;  
  117.         byte[] bToEncrypt = CryptoHelper.ConvertStringToByteArray(sToEncrypt);  
  118.   
  119.         CryptoService cs = GetCryptoService();  
  120.         byte[] encrypted;  
  121.   
  122.         if (cs.Encrypt(bToEncrypt, out encrypted))  
  123.         {  
  124.             ssoRequest.Authenticator = CryptoHelper.ToBase64String(encrypted);  
  125.   
  126.             return true;  
  127.         }  
  128.         else  
  129.         {  
  130.             return false;  
  131.         }  
  132.     }  
  133.   
  134.   
  135.     /// <summary>  
  136.     /// 验证从认证中心发送过来的 Token  
  137.     /// </summary>  
  138.     /// <param name="ssoRequest"></param>  
  139.     /// <returns></returns>  
  140.     public static bool ValidateEACToken(SSORequest ssoRequest)  
  141.     {  
  142.         string Authenticator = ssoRequest.Authenticator;  
  143.   
  144.         string OriginalAuthenticator = ssoRequest.UserAccount + ssoRequest.IASID + ssoRequest.TimeStamp + ssoRequest.AppUrl;  
  145.         string AuthenticatorDigest = CryptoHelper.ComputeHashString(OriginalAuthenticator);  
  146.         string sToEncrypt = OriginalAuthenticator + AuthenticatorDigest;  
  147.         byte[] bToEncrypt = CryptoHelper.ConvertStringToByteArray(sToEncrypt);  
  148.   
  149.         string EncryCurrentAuthenticator = string.Empty;  
  150.         CryptoService cs = GetCryptoService();  
  151.         byte[] encrypted;  
  152.   
  153.         if (cs.Encrypt(bToEncrypt, out encrypted))  
  154.         {  
  155.             EncryCurrentAuthenticator = CryptoHelper.ToBase64String(encrypted);  
  156.   
  157.             return Authenticator == EncryCurrentAuthenticator;  
  158.         }  
  159.         else  
  160.         {  
  161.             return false;  
  162.         }  
  163.     }  
  164.   
  165.   
  166.     /// <summary>  
  167.     /// 创建 EAC 认证中心的 Cookie  
  168.     /// </summary>  
  169.     /// <param name="userAccount"></param>  
  170.     /// <param name="timeStamp"></param>  
  171.     /// <param name="expireTime"></param>  
  172.     /// <param name="cookieValue"></param>  
  173.     /// <returns></returns>  
  174.     public static bool CreatEACCookie(string userAccount, string timeStamp, string expireTime)  
  175.     {  
  176.         string plainText = "UserAccount=" + userAccount + ";TimeStamp=" + timeStamp + ";ExpireTime=" + expireTime;  
  177.         plainText += hashSplitter + CryptoHelper.ComputeHashString(plainText);  
  178.   
  179.         CryptoService cs = GetCryptoService();  
  180.         byte[] encrypted;  
  181.   
  182.         if (cs.Encrypt(CryptoHelper.ConvertStringToByteArray(plainText), out encrypted))  
  183.         {  
  184.             string cookieValue = CryptoHelper.ToBase64String(encrypted);  
  185.             SetCookie(cookieValue);  
  186.   
  187.             return true;  
  188.         }  
  189.         else  
  190.         {  
  191.             return false;  
  192.         }  
  193.     }  
  194.   
  195.     /// <summary>  
  196.     /// 验证 EAC 认证中心的 Cookie,验证通过时获取用户登录账号  
  197.     /// </summary>  
  198.     /// <param name="userAccount">输出用户登录账号</param>  
  199.     /// <returns></returns>  
  200.     public static bool ValidateEACCookie(out string userAccount)  
  201.     {  
  202.         userAccount = string.Empty;  
  203.         try  
  204.         {  
  205.   
  206.             string cookieValue = GetCookie().Value;  
  207.             byte[] toDecrypt = CryptoHelper.FromBase64String(cookieValue);  
  208.             CryptoService cs = GetCryptoService();  
  209.   
  210.             string decrypted = string.Empty;  
  211.             if (cs.Decrypt(toDecrypt, out decrypted))  
  212.             {  
  213.   
  214.                 string[] arrTemp = decrypted.Split(Convert.ToChar(hashSplitter));  
  215.                 string plainText = arrTemp[0];  
  216.                 string hashedText = arrTemp[1];  
  217.   
  218.                 userAccount = plainText.Split(Convert.ToChar(";"))[0].Split(Convert.ToChar("="))[1];  
  219.   
  220.                 return hashedText.Replace("/0"string.Empty) == CryptoHelper.ComputeHashString(plainText);  
  221.   
  222.             }  
  223.             else  
  224.             {  
  225.                 return false;  
  226.             }  
  227.         }  
  228.         catch (Exception e)  
  229.         {  
  230.             return false;  
  231.         }  
  232.     }  
  233.   
  234.   
  235.     public static void Logout()  
  236.     {  
  237.         HttpContext.Current.Response.Cookies[cookieName].Expires = DateTime.Parse("1900-1-1");  
  238.         HttpContext.Current.Response.Cookies[cookieName].Path = "/";  
  239.     }  
  240.   
  241.     private static void SetCookie(string cookieValue)  
  242.     {  
  243.         HttpContext.Current.Response.Cookies[cookieName].Value = cookieValue;  
  244.         HttpContext.Current.Response.Cookies[cookieName].Expires = DateTime.Now.AddHours(24);  
  245.         HttpContext.Current.Response.Cookies[cookieName].Path = "/";  
  246.     }  
  247.   
  248.     private static HttpCookie GetCookie()  
  249.     {  
  250.         HttpCookie cookie = HttpContext.Current.Request.Cookies["EACToken"];  
  251.         return cookie;  
  252.     }  
  253. }  

 

CryptoHelper.cs

 

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Text;  
  4. using System.Security.Cryptography;  
  5.   
  6. public class CryptoHelper  
  7. {  
  8.     /// <summary>  
  9.     /// 复合 Hash:string --> byte[] --> hashed byte[] --> base64 string  
  10.     /// </summary>  
  11.     /// <param name="s"></param>  
  12.     /// <returns></returns>  
  13.     public static string ComputeHashString(string s)  
  14.     {  
  15.         return ToBase64String(ComputeHash(ConvertStringToByteArray(s)));  
  16.     }  
  17.   
  18.   
  19.     public static byte[] ComputeHash(byte[] buf)  
  20.     {  
  21.         //return ((HashAlgorithm)CryptoConfig.CreateFromName("SHA1")).ComputeHash(buf);  
  22.         return SHA1.Create().ComputeHash(buf);  
  23.   
  24.     }  
  25.   
  26.     /// <summary>  
  27.     /// //System.Convert.ToBase64String  
  28.     /// </summary>  
  29.     /// <param name="buf"></param>  
  30.     /// <returns></returns>  
  31.     public static string ToBase64String(byte[] buf)  
  32.     {  
  33.         return System.Convert.ToBase64String(buf);  
  34.     }  
  35.   
  36.   
  37.     public static byte[] FromBase64String(string s)  
  38.     {  
  39.         return System.Convert.FromBase64String(s);  
  40.     }  
  41.   
  42.     /// <summary>  
  43.     /// //Encoding.UTF8.GetBytes(s)  
  44.     /// </summary>  
  45.     /// <param name="s"></param>  
  46.     /// <returns></returns>  
  47.     public static byte[] ConvertStringToByteArray(String s)  
  48.     {  
  49.         return Encoding.UTF8.GetBytes(s);//gb2312  
  50.     }  
  51.   
  52.   
  53.     public static string ConvertByteArrayToString(byte[] buf)  
  54.     {  
  55.         //return System.Text.Encoding.GetEncoding("utf-8").GetString(buf);  
  56.   
  57.         return Encoding.UTF8.GetString(buf);  
  58.     }  
  59.   
  60.   
  61.     /// <summary>  
  62.     /// 字节数组转换为十六进制字符串  
  63.     /// </summary>  
  64.     /// <param name="buf"></param>  
  65.     /// <returns></returns>  
  66.     public static string ByteArrayToHexString(byte[] buf)  
  67.     {  
  68.         StringBuilder sb = new StringBuilder();  
  69.         for (int i = 0; i < buf.Length; i++)  
  70.         {  
  71.             sb.Append(buf[i].ToString("X").Length == 2 ? buf[i].ToString("X") : "0" + buf[i].ToString("X"));  
  72.         }  
  73.         return sb.ToString();  
  74.     }  
  75.   
  76.     /// <summary>  
  77.     /// 十六进制字符串转换为字节数组  
  78.     /// </summary>  
  79.     /// <param name="s"></param>  
  80.     /// <returns></returns>  
  81.     public static byte[] HexStringToByteArray(string s)  
  82.     {  
  83.         Byte[] buf = new byte[s.Length / 2];  
  84.         for (int i = 0; i < buf.Length; i++)  
  85.         {  
  86.             buf[i] = (byte)(Char2Hex(s.Substring(i * 2, 1)) * 0x10 + Char2Hex(s.Substring(i * 2 + 1, 1)));  
  87.         }  
  88.         return buf;  
  89.     }  
  90.   
  91.   
  92.     private static byte Char2Hex(string chr)  
  93.     {  
  94.         switch (chr)  
  95.         {  
  96.             case "0":  
  97.                 return 0x00;  
  98.             case "1":  
  99.                 return 0x01;  
  100.             case "2":  
  101.                 return 0x02;  
  102.             case "3":  
  103.                 return 0x03;  
  104.             case "4":  
  105.                 return 0x04;  
  106.             case "5":  
  107.                 return 0x05;  
  108.             case "6":  
  109.                 return 0x06;  
  110.             case "7":  
  111.                 return 0x07;  
  112.             case "8":  
  113.                 return 0x08;  
  114.             case "9":  
  115.                 return 0x09;  
  116.             case "A":  
  117.                 return 0x0a;  
  118.             case "B":  
  119.                 return 0x0b;  
  120.             case "C":  
  121.                 return 0x0c;  
  122.             case "D":  
  123.                 return 0x0d;  
  124.             case "E":  
  125.                 return 0x0e;  
  126.             case "F":  
  127.                 return 0x0f;  
  128.         }  
  129.         return 0x00;  
  130.     }  
  131. }  

 

 

CryptoService.cs

 

  1. using System;  
  2. using System.Data;  
  3. using System.Configuration;  
  4. using System.Web;  
  5. using System.Web.Security;  
  6. using System.Web.UI;  
  7. using System.Web.UI.WebControls;  
  8. using System.Web.UI.WebControls.WebParts;  
  9. using System.Web.UI.HtmlControls;  
  10. using System.Text;  
  11. using System.Security.Cryptography;  
  12. using System.IO;  
  13.   
  14. public class CryptoService  
  15. {  
  16.     /// <summary>  
  17.     /// 加密的密钥  
  18.     /// </summary>  
  19.     string sKey = "22362E7A9285DD53A0BBC2932F9733C505DC04EDBFE00D70";  
  20.     string sIV = "1E7FA9231E7FA923";  
  21.   
  22.     byte[] byteKey;  
  23.     byte[] byteIV;  
  24.   
  25.     /// <summary>  
  26.     /// 加密向量  
  27.     /// </summary>  
  28.     static byte[] bIV ={ 1, 2, 3, 4, 5, 6, 7, 8 };  
  29.   
  30.     public CryptoService()  
  31.     { }  
  32.   
  33.     public CryptoService(string key, string IV)  
  34.     {  
  35.         sKey = key;  
  36.         sIV = IV;  
  37.   
  38.         byteKey = CryptoHelper.HexStringToByteArray(sKey);  
  39.         byteIV = CryptoHelper.HexStringToByteArray(sIV);  
  40.     }  
  41.   
  42.   
  43.   
  44.     /// <summary>  
  45.     /// 将明文加密,返回密文  
  46.     /// </summary>  
  47.     /// <param name="Data">要加密的字串</param>  
  48.     /// <returns></returns>  
  49.     public byte[] Encrypt(string Data)  
  50.     {  
  51.         try  
  52.         {  
  53.             byte[] ret;  
  54.   
  55.             using (MemoryStream mStream = new MemoryStream())  
  56.             using (CryptoStream cStream = new CryptoStream(mStream,  
  57.                 new TripleDESCryptoServiceProvider().CreateEncryptor(byteKey, byteIV),  
  58.                 CryptoStreamMode.Write))  
  59.             {  
  60.   
  61.                 byte[] toEncrypt = new ASCIIEncoding().GetBytes(Data);  
  62.   
  63.                 // Write the byte array to the crypto stream and flush it.  
  64.                 cStream.Write(toEncrypt, 0, toEncrypt.Length);  
  65.                 cStream.FlushFinalBlock();  
  66.   
  67.                 // Get an array of bytes from the   
  68.                 // MemoryStream that holds the   
  69.                 // encrypted data.  
  70.                 ret = mStream.ToArray();  
  71.   
  72.             }  
  73.   
  74.             return ret;  
  75.         }  
  76.         catch (CryptographicException e)  
  77.         {  
  78.             //Console.WriteLine("A Cryptographic error occurred: {0}", e.Message);  
  79.             return null;  
  80.         }  
  81.   
  82.     }  
  83.   
  84.   
  85.     /// <summary>  
  86.     /// 将明文加密,返回密文  
  87.     /// </summary>  
  88.     /// <param name="toEncrypt">明文</param>  
  89.     /// <param name="encrypted">密文</param>  
  90.     /// <returns></returns>  
  91.     public bool Encrypt(byte[] toEncrypt, out byte[] encrypted)  
  92.     {  
  93.         encrypted = null;  
  94.         try  
  95.         {  
  96.             // Create a new MemoryStream using the passed   
  97.             // array of encrypted data.  
  98.             // Create a CryptoStream using the MemoryStream   
  99.             // and the passed key and initialization vector (IV).  
  100.             using (MemoryStream mStream = new MemoryStream())  
  101.             using (CryptoStream cStream = new CryptoStream(mStream,  
  102.                 new TripleDESCryptoServiceProvider().CreateEncryptor(byteKey, byteIV),  
  103.                 CryptoStreamMode.Write))  
  104.             {  
  105.   
  106.                 // Write the byte array to the crypto stream and flush it.  
  107.                 cStream.Write(toEncrypt, 0, toEncrypt.Length);  
  108.                 cStream.FlushFinalBlock();  
  109.   
  110.                 // Get an array of bytes from the   
  111.                 // MemoryStream that holds the   
  112.                 // encrypted data.  
  113.                 encrypted = mStream.ToArray();  
  114.             }  
  115.   
  116.             return true;  
  117.         }  
  118.         catch (CryptographicException e)  
  119.         {  
  120.             //Console.WriteLine("A Cryptographic error occurred: {0}", e.Message);  
  121.             return false;  
  122.         }  
  123.   
  124.     }  
  125.   
  126.   
  127.   
  128.     /// <summary>  
  129.     /// 将明文加密,返回 Base64 字符串  
  130.     /// </summary>  
  131.     /// <param name="Data"></param>  
  132.     /// <returns></returns>  
  133.     public string EncryptToString(string Data)  
  134.     {  
  135.         try  
  136.         {  
  137.             string base64String = string.Empty;  
  138.   
  139.             using (MemoryStream mStream = new MemoryStream())  
  140.             using (CryptoStream cStream = new CryptoStream(mStream,  
  141.                 new TripleDESCryptoServiceProvider().CreateEncryptor(byteKey, byteIV),  
  142.                 CryptoStreamMode.Write))  
  143.             {  
  144.   
  145.                 byte[] toEncrypt = new ASCIIEncoding().GetBytes(Data);  
  146.   
  147.                 cStream.Write(toEncrypt, 0, toEncrypt.Length);  
  148.                 cStream.FlushFinalBlock();  
  149.   
  150.                 byte[] ret = mStream.ToArray();  
  151.   
  152.                 base64String = Convert.ToBase64String(ret);  
  153.             }  
  154.   
  155.             return base64String;  
  156.         }  
  157.         catch (CryptographicException e)  
  158.         {  
  159.             return null;  
  160.         }  
  161.   
  162.     }  
  163.   
  164.   
  165.     /// <summary>  
  166.     /// 将密文解密,返回明文  
  167.     /// </summary>  
  168.     /// <param name="Data">密文</param>  
  169.     /// <returns>明文</returns>  
  170.     public bool Decrypt(byte[] Data, out string decrypted)  
  171.     {  
  172.         decrypted = string.Empty;  
  173.         try  
  174.         {  
  175.   
  176.             using (MemoryStream msDecrypt = new MemoryStream(Data))  
  177.             using (CryptoStream csDecrypt = new CryptoStream(msDecrypt,  
  178.                 new TripleDESCryptoServiceProvider().CreateDecryptor(byteKey, byteIV),  
  179.                 CryptoStreamMode.Read))  
  180.             {  
  181.   
  182.                 byte[] fromEncrypt = new byte[Data.Length];  
  183.   
  184.                 // Read the decrypted data out of the crypto stream  
  185.                 // and place it into the temporary buffer.  
  186.                 csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);  
  187.   
  188.                 decrypted = Encoding.UTF8.GetString(fromEncrypt);//new ASCIIEncoding().GetString(fromEncrypt);  
  189.   
  190.                 return true;  
  191.             }  
  192.         }  
  193.         catch (CryptographicException e)  
  194.         {  
  195.             return false;  
  196.         }  
  197.     }  
  198.   
  199. }  
  

 

 

PostService.cs

 

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Text;  
  4.   
  5. public class PostService  
  6. {  
  7.     private System.Collections.Specialized.NameValueCollection Inputs = new System.Collections.Specialized.NameValueCollection();  
  8.     public string Url = "";  
  9.     public string Method = "post";  
  10.     public string FormName = "form1";  
  11.   
  12.     /// <summary>  
  13.     /// 添加需要提交的名和值  
  14.     /// </summary>  
  15.     /// <param name="name"></param>  
  16.     /// <param name="value"></param>  
  17.     public void Add(string name, string value)  
  18.     {  
  19.         Inputs.Add(name, value);  
  20.     }  
  21.   
  22.     /// <summary>  
  23.     /// 以输出Html方式POST  
  24.     /// </summary>  
  25.     public void Post()  
  26.     {  
  27.         System.Web.HttpContext.Current.Response.Clear();  
  28.   
  29.         string html = string.Empty;  
  30.   
  31.         html += ("<html><head>");  
  32.         html += (string.Format("</head><body onload=/"document.{0}.submit()/">", FormName));  
  33.         html += (string.Format("<form name=/"{0}/" method=/"{1}/" action=/"{2}/" >", FormName, Method, Url));  
  34.         try  
  35.         {  
  36.             for (int i = 0; i < Inputs.Keys.Count; i++)  
  37.             {  
  38.                 html += (string.Format("<input name=/"{0}/" type=/"hidden/" value=/"{1}/">", Inputs.Keys[i], Inputs[Inputs.Keys[i]]));  
  39.             }  
  40.             html += ("</form>");  
  41.             html += ("</body></html>");  
  42.   
  43.             System.Web.HttpContext.Current.Response.Write(html);  
  44.             System.Web.HttpContext.Current.Response.End();  
  45.         }  
  46.         catch (Exception ee)  
  47.         {  
  48.             //  
  49.         }  
  50.     }  
  51. }  

 

 

SSORequest.cs

 

  1. using System;  
  2. using System.Data;  
  3. using System.Configuration;  
  4. using System.Web;  
  5. using System.Web.Security;  
  6. using System.Web.UI;  
  7. using System.Web.UI.WebControls;  
  8. using System.Web.UI.WebControls.WebParts;  
  9. using System.Web.UI.HtmlControls;  
  10.   
  11. [Serializable]  
  12. public class SSORequest : MarshalByRefObject  
  13. {  
  14.     public string IASID;         //各独立站点标识ID  
  15.     public string TimeStamp;     //时间戳  
  16.     public string AppUrl;        //各独立站点的访问地址  
  17.     public string Authenticator; //各独立站点的 Token  
  18.   
  19.     public string UserAccount;   //账号  
  20.     public string Password;      //密码  
  21.   
  22.     public string IPAddress;     //IP地址  
  23.   
  24.     //为ssresponse对象做准备  
  25.     public string ErrorDescription = "认证失败";   //用户认证通过,认证失败,包数据格式不正确,数据校验不正确  
  26.     public int Result = -1;  
  27.   
  28.     public SSORequest()  
  29.     {  
  30.   
  31.     }  
  32.   
  33.   
  34.     /// <summary>  
  35.     /// 获取当前页面上的SSORequest对象  
  36.     /// </summary>  
  37.     /// <param name="CurrentPage"></param>  
  38.     /// <returns></returns>  
  39.     public static SSORequest GetRequest(Page CurrentPage)  
  40.     {  
  41.         SSORequest request = new SSORequest();  
  42.         request.IPAddress = CurrentPage.Request.UserHostAddress;  
  43.         request.IASID = CurrentPage.Request["IASID"].ToString();// Request本身会Decode  
  44.         request.UserAccount = CurrentPage.Request["UserAccount"].ToString();//this.Text  
  45.         request.Password = CurrentPage.Request["Password"].ToString();  
  46.         request.AppUrl = CurrentPage.Request["AppUrl"].ToString();  
  47.         request.Authenticator = CurrentPage.Request["Authenticator"].ToString();  
  48.         request.TimeStamp = CurrentPage.Request["TimeStamp"].ToString();  
  49.         return request;  
  50.     }  
  51. }  
  

 

 

配置web.config

 

  1. <authentication mode="Forms">  
  2.             <forms name=".AspxFormAuth" loginUrl="Default.aspx" defaultUrl="center.html" protection="All" path="/" timeout="60">  
  3.             </forms>  
  4.         </authentication>  
  5.         <authorization>  
  6.             <!--拒绝所有匿名用户-->  
  7.             <deny users="?"/>  
  8.         </authorization>  

 

 

最后效果如下:登录总站后,各站点之间无需再登录,可以互相访问。

 

 

 

 

 

 

 

 

另外,注销登录后,访问站点1 http://localhost/Site1/Default.aspx ,会自动跳转到主站登录页面 http://localhost/MasterSite/Default.aspx ,同样访问站点2 http://localhost/Site2/Default.aspx 也会转到主站登录页面。从主站登录后,分别访问站点1和站点2。

 

在IIS配置虚拟目录MasterSite Site1 Site2,当然你也可以新建站点MasterSite Site1 Site2,修改hosts表
127.0.0.1      http://www.mastersite.com/

127.0.0.1      http://www.site1.com/

127.0.0.1      http://www.site2.com/

 

 

源代码下载:http://download.csdn.net/source/1571879 

 

转载出处:http://blog.csdn.net/slimboy123/archive/2009/08/13/4442972.aspx

posted on 2015-01-29 14:35  itjeff  阅读(441)  评论(0编辑  收藏  举报

导航