单点接入方案总结

单点登录首先获取统一门户的登陆凭据,然后通过验证取得登陆名,最后对登陆名在接入系统中进行验证并创建会话。也有一种是约定的方式,比如根据几组串包括时间戳、密钥等进行加密,然后用约定的方式解密获得数据。老式的单点通过域名方式将令牌存储在Cookie中,该方式不支持IP方式或者跨域,有一定局限。新式的单点运用重定向技术,由门户系统自动附加到应用系统接入接口,不受IP或者协议本身限制。

下面对几种常见的单点方式进行总结。

CAS登陆

CAS登陆包含登陆地址、验证地址以及退出地址。接入的应用系统首先需要登记通过参数service识别。

示例代码用C#编写,其他语言均可以改写。底层原理都可以使用抓包的方式分析。

没有会话的时候,就去访问CAS登陆地址进行登陆,通过参数service。验证成功后,CAS会回调service地址并将ticket参数提供给应用。通过获取的ticket以及service进行去验证,验证成功返回响应的XML包含user即登陆名。

一般情况下CAS的地址形如:

登陆:……/cas目录/login

验证:……/cas目录/proxyValidate

注销:……/cas目录/logout

public string getHost()

{

 Boolean https=Request.ServerVariables["HTTPS"].ToLower().Equals("on");

   String Host=Request.ServerVariables["HTTP_HOST"];

   return (https ? "https://" : "http://") + Host;

}

public String getEmployeeID()

{

  string loginaspx =HttpUtility.UrlEncode(getHost()+Request.ServerVariables["PATH_INFO"]) ;

  string ticket = Request.QueryString["ticket"];

  if (ticket == null || ticket.Length == 0)

  {

      Response.Redirect(loginServer + "?service=" + loginaspx);

      return null;

  }

  string validateUrl = validateServer + "?ticket=" + ticket + "&service=" + loginaspx;

 ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult);

   //验证返回的ticket,获取服务器返回的用户名

   try

   {

      StreamReader Reader = new StreamReader(new WebClient().OpenRead(validateUrl));

      string resp = Reader.ReadToEnd();

      NameTable nt = new NameTable();

      XmlNamespaceManager nsmgr = new XmlNamespaceManager(nt);

      XmlParserContext context = new XmlParserContext(null, nsmgr, null, XmlSpace.None);

     XmlTextReader reader = new XmlTextReader(resp, XmlNodeType.Element, context);

      string uid = null;

      while (reader.Read())

      {

          if (reader.IsStartElement())

          {

               string tag = reader.LocalName;

               if (tag == "user")

                   uid = reader.ReadString();

           }

       }

       reader.Close();

       return uid;

   }

   catch (Exception ee)

   {

        return null;

   }  

}

OAuth登陆

OAuth登陆方式,门户会提供相关接口,一般会提供约定的appKey、appPassword,通过响应JSON来获取信息。与CAS类似,第一步仍然需要通过接口获取accessToken,然后验证accessToken获取登陆名。

下面是C#代码示例,同时使用一个获取门户在线用户的方法,这样可以避免每次重复去登陆验证。

    public class JsonObjectAccessToken

    {

        public int type;

        public string access_token;

    }

   public class JsonObjectSession

    {

        public string status;

        public string desc;

        public Dictionary<string, string> obj;

    }

  public String getEmployeeID()

    {

       String appKey = configNode.Attributes["appKey"].Value;

       String appPassword = configNode.Attributes["appPassword"].Value;

       String serverName = configNode.Attributes["serverName"].Value;          //本地登录入口

      string redirectUrl=HostName+Request.ServerVariables["PATH_INFO"];         string ticket = Request.QueryString["code"];

        if (ticket == null || ticket.Length == 0)

        {

            //如果门户已登陆直接返回登陆信息

            String loginUserResponse = UserInfo.Text.Replace("callback(", "").Replace(")", "");

            if (loginUserResponse != null && loginUserResponse.Equals("") == false)

            {

                JsonObjectSession loginUserObject = JsonConvert.DeserializeObject<JsonObjectSession>(loginUserResponse);

                if (loginUserObject != null && loginUserObject.status.Equals("1"))

                {

                    return loginUserObject.obj["LoginName"];

                }

            }

            if (Session["ticket"] != null)

            {

                ticket = Session["ticket"].ToString();

            }

            if (ticket == null || ticket.Length == 0)

            {

                Response.Redirect(serverName + "/OAuth2/OAuth?client_id=" + appKey + "&redirect_uri=" + redirectUrl + "&forcelogin=false&state=STATE");

                return null;

            }

        }

        Session["ticket"] = ticket;

        string validateUrl = serverName + "/OAuth2/access_token?client_id=" + appKey + "&client_secret="+appPassword+"&redirect_uri=" + redirectUrl+"&code="+ticket;

        //验证返回的ticket,获取服务器返回的用户名

        try

        {

            String accessTokenResponse = getResponse(validateUrl);

            JsonObjectAccessToken accessTokenObject = JsonConvert.DeserializeObject<JsonObjectAccessToken>(accessTokenResponse);

            if (accessTokenObject != null)

            {

                if (accessTokenObject.type == 1)

                {

                   String accessToken = accessTokenObject.access_token;

                    //请求用户信息

                    String sessionUrl = serverName + "/SSOService.asmx/user_base_info?access_token=" + accessToken;

                    String sessionResponse = getResponse(sessionUrl).Replace("(","").Replace(")","");

                    JsonObjectSession sessionObject = JsonConvert.DeserializeObject<JsonObjectSession>(sessionResponse);

                    if (sessionObject.status.Equals("1"))

                    {

  return sessionObject.obj["UserLoginName"];

                    }

                    

                }

               if (Session["ticket"] != null) Session.Remove("ticket");

            }            

        }

        catch (Exception e)

        {

            Response.Write(e.StackTrace);            

        }

        return null;  

      

    }

    public static String getTimestamp()

    {

        System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1, 0, 0, 0, 0));

        long t = (DateTime.Now.Ticks - startTime.Ticks) / 10000;

        return t.ToString();

    }

 public string getResponse(String url)

    {

        Stream stream = null;

        StreamReader Reader = null;

        string responseMsg = "";

        try

        {

   HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);   request.Method = "GET";              //以下可以自定义请求头部

     request.ContentType = "text/json;charset=utf-8";

     request.Timeout = 1000;

     request.Referer = HostName;

     HttpWebResponse response = (HttpWebResponse)request.GetResponse();

     stream = response.GetResponseStream();

     Reader = new StreamReader(stream, System.Text.Encoding.GetEncoding("utf-8"));//自行进行编码转换

    responseMsg = Reader.ReadToEnd();  //这里决定了 肯定不会出现EndofStream异常。

        }

        catch

        {

        }

        finally

        {

            if (Reader != null)

            {

                Reader.Close();

                Reader.Dispose();

            }

            Reader = null;

            if (stream != null)

            {

                stream.Close();

                stream.Dispose();

            }

            stream = null;

        } 

        return responseMsg;

    }

 

自定义加密

    public string getMD5(String plainText)

    {

        System.Security.Cryptography.MD5 key=System.Security.Cryptography.MD5.Create();

        Byte[] bytes = key.ComputeHash(System.Text.Encoding.UTF8.GetBytes(plainText));

        System.Text.StringBuilder builder = new System.Text.StringBuilder();

        foreach(Byte _byte in bytes)

        {

            builder.Append(_byte.ToString("x2").ToUpper());

        }

        return builder.ToString();

    }

 

    String verify=Request.QueryString["verify"];

    String userName=Request.QueryString["userName"];

    String strSysDatetime=Request.QueryString["strSysDatetime"];

    String jsName=Request.QueryString["jsName"];

    String key=……;

  if (getMD5(userName + key + strSysDatetime + jsName).Equals(verify))

    {

          return userName;

    }

 

门户与各接入应用系统之间的账户要实现同步,包括新增用户以及删除用户。比较完善的系统,还将涉及到密码的同步。

单点的方式本质不是很安全,可以伪造并进行模拟登陆。安全性最关键的一环就是验证。一旦获得用户名,后面都将放行了。

posted @ 2020-01-24 12:28  yuxiaoxu  阅读(414)  评论(0编辑  收藏  举报