zoukankan      html  css  js  c++  java
  • asp.net 跨域单点登录 【转】

    关键字:单点登录   跨域    跨域单点登录

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

    单点登录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);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("", 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>  
    
    <authentication mode="Forms">
                <forms name=".AspxFormAuth" loginUrl="Default.aspx" defaultUrl="center.html" protection="All" path="/" timeout="60">
                </forms>
            </authentication>
            <authorization>
                <!--拒绝所有匿名用户-->
                <deny users="?"/>
            </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 

  • 相关阅读:
    iOS开发之结合asp.net webservice实现文件上传下载
    iOS开发之结合asp.net webservice实现文件上传下载
    底部粘连(stiky footer)布局
    底部粘连(stiky footer)布局
    我在项目中运用 IOC(依赖注入)--实战篇
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    Facebook Libra 白皮书全文:采用拜占庭共识,可扩展至数十亿客户
    DBA遇到问题时的30 个反应,你是哪一种?
  • 原文地址:https://www.cnblogs.com/shengfa/p/4023003.html
Copyright © 2011-2022 走看看