CAS单点登录客户端--C++版

前言:
如果用java来写一个单点登录的CAS客户端,是很容易实现的,只要导入相关的jar包,再按要求配置就好了,关于java的单点登录的实现,在我前边的几篇文章中有重点描述,有兴趣的童鞋可以去看看。最近要做一个c++版的单点登录的客户端,在网上搜了一下,没有找到,所以只好自己想办法写了。

1.单点登录原理

为了了解单点登录的原理,我反编译了java版客户端jar包cas-client.jar代码,以下描述只是我的理解,如果有不当的地方,还望大家指出。

I.当用户第一次去访问客户端地址clientURL时,cas客户端检测到session中没有用户信息且浏览器中没有ticket,会重定向到casServer的地址serverURL,地址格式为serverURL/login?service=clientURL,service代表用户在server端成功登陆后页面跳转的地址,即客户端地址。
II.服务端接收到请求,判断到请求中没有ticket,会跳转到登录页面,用户在页面上填写正确的用户名密码完成登录后,页面跳转到客户端地址clientURL,格式为clientURL?ticket=XXX,并将ticket保存到浏览器缓存中(ticket的保存应该是由casServer完成的)。
III.此时地址跳回了客户端,cas客户端判断到此时session中没有用户信息,但请求中携带了ticket,因此向casServer发送ticket校验的请求,请求格式为serverURL/serviceValidate?ticket=XXX&service=clientURL,使用的方法是HTTPURLConnection.如果ticket验证成功,会返回如下xml文件格式的数据:

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    <cas:authenticationSuccess>
        <cas:user>sa</cas:user>   
    </cas:authenticationSuccess>
</cas:serviceResponse>

IV.然后客户端通过解析该数据,获取用户名,并存储到session中。
当用户再次登录该系统时,判断到session中有用户信息,所以不再进行ticket验证;如果去登陆另外一个cas客户端,由于浏览器中存在ticket,所以会直接进行ticket验证的操作。
以上简单的对该流程进行了描述,下面的代码使用java是对ticket校验过程的简单模拟:

public class HttpUrlConnectionTest {

    /**
     * @param args
     * @throws UnsupportedEncodingException 
     */
    public static void main(String[] args) throws Exception {
        //cas server 校验地址
        String serverValidateUrl="http://nagsh:8080/cas_server/serviceValidate";
        //客户端地址
        String service = "http://nagsh:8080/cas_cgi_client/cgi-bin/cas_client2.exe";
        //ticket
        String ticket ="ST-19-eBhtXb5cuIlAxHpqf5pA-cas01.example.org";

        //组装url
        String constructServiceUrl  = serverValidateUrl+"?ticket="+ticket+"&service="+service;
        URL url =new URL(constructServiceUrl);
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        conn.setConnectTimeout(5*1000);
        conn.setRequestMethod("GET");
        InputStream inStream = conn.getInputStream();    
        final StringBuilder builder = new StringBuilder(255);
        int byteRead;
        while ((byteRead = inStream.read()) != -1) {
            builder.append((char) byteRead);
        }
        String response = builder.toString();

    }

}

获取到response后再使用XmlUtils去解析即可。
这里不对cas_client反编译的源码做过多的描述,有兴趣的童鞋可以去看一下源码,会有更多的收获

2.开发环境

开发前有很多准备工作,我也是一步步摸索过来的。
I.搭建CGI环境
c++一般是用来写后台的,一般编译后会形成exe文件,但我需要将其部署到tomcat下,所以需要配置tomcat使其支持c++,具体内容,请看我的另一篇文章:CGI编程–Tomcat下运行c++程序

II.编译libcurl,cgicc,tinyxml三个库
关于这三个库,在文章末尾的源码中都有,包括dll文件,需要将其中的dll文件拷贝到C:\Windows\SysWOW64目录下。

III.tomcat8
tomcat6不支持libcurl,在这个问题上我花费了两天时间,一直以为是代码的问题,后来换成tomcat8就好了。

IV.vs2008
我使用的开发工具是vs2008.

V.部署casServer.
在我的另外两篇文章中有casServer的配置方法,如果大家有现成的casServer,可以忽略此步骤。
CAS单点登录(二)—非SSL协议 CAS服务端部署及客户端配置
CAS单点登录(三)–服务端改造(登录页及登录方式的自定义)

3. 代码

其实,拿代码说话才是硬道理。
I.先来一张代码结构图:
这里写图片描述
cgicc、curl、tinyxml三个文件夹下是对应的头文件和cpp文件。
cas.client.h为核心,我把它写成了头文件。
config.xml为配置文件。
lib下为依赖的库文件,需要在vs中做依赖关联。
dll下为dll文件,需要添加到C:\Windows\SysWOW64下。
main.cpp是程序的入口,主要是调用cas_client.h。

II.config.xml:

<xml>
    <CAS_SERVER_URL>http://127.0.0.1/cas_server</CAS_SERVER_URL>
    <CAS_CLIENT_URL>http://nagsh:8080/cas_cgi_client/cgi-bin/cas_client.exe</CAS_CLIENT_URL>
</xml>

III.cas_client.h

#include <iostream>
#include <vector> 
#include <stdio.h>  
#include <stdlib.h>  
#include <string>  
#include <stddef.h>  

#include "cgicc/CgiDefs.h" 
#include "cgicc/Cgicc.h" 
#include "cgicc/HTTPHTMLHeader.h" 
#include "cgicc/HTMLClasses.h" 
#include "curl/curl.h"
#include "tinyxml/tinyxml.h"


using namespace std;
using namespace cgicc;

string CAS_SERVER_URL = "";  //CAS server 地址
string CAS_CLIENT_URL = "";  //CAS 客户端地址
string fileUrl = "config.xml";
//读取配置文件,获取配置信息
void loadInfo(){
    const char * xmlUrl = fileUrl.c_str();
    TiXmlDocument doc(xmlUrl);
    bool loadOk = doc.LoadFile();
    if (!loadOk)
    {
        cout << "could load:" << doc.ErrorDesc() << endl;
    }
    //获取配置信息
    TiXmlElement * rootElement = doc.RootElement(); //serviceResponse元素
    TiXmlElement * serverElement = rootElement->FirstChildElement();  // cas_server元素
    TiXmlElement* clientElement = serverElement->NextSibling()->ToElement();  // cas_server元素  
    const char * cas_server = serverElement->GetText();
    const char * cas_client = clientElement->GetText();
    string serverStr(cas_server);
    string clientStr(cas_client);
    CAS_SERVER_URL = serverStr;
    CAS_CLIENT_URL = clientStr;
}

//CAS客户端页面展示
void toHomePage(string info){
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>CAS 客户端</title>\n";
   cout << "<script type=\"text/javascript\">\n";
   cout << "function logout(){";
   cout << "document.cookie='userName=;';";
  // cout << "window.location.href=encodeURI(\"http://127.0.0.1:8080/cas_server/logout?service=http://nagsh:8080/cas_cgi_client/cgi-bin/cas_client2.exe\");";//登出需要用IP 否则报错
   //此处为注销的方法
   cout <<"window.location.href=encodeURI(\""+CAS_SERVER_URL+"/logout?service="+CAS_CLIENT_URL+"\");" ;
   cout <<"}";
   cout << "</script>\n";
   cout << "</head>\n";
   cout << "<body>\n";
   cout << "welcome:"+info+"";
   cout << "<a href='javascript:;' onclick='logout()'>注销</a>";
   cout << "</body>\n";
   cout << "</html>\n";
}
//重定向到CAS 客户端
void rediretToClient(string info){ 
   //保存cookie后重定向
   string cookies = "document.cookie='userName="+info+"';";
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<script type=\"text/javascript\">\n";
   cout << cookies;
   cout << "window.location.href=encodeURI(\""+CAS_CLIENT_URL+"\")\n";
   cout << "</script>\n";
   cout << "</head>\n";
   cout << "</html>\n";
}
//没有ticket 重定向到CAS 服务端 
void toServerPage(){
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<script type=\"text/javascript\">\n";
   //cout << "window.location.href=encodeURI(\"http://nagsh:8080/cas_server/login?service=http://nagsh:8080/cas_cgi_client/cgi-bin/cas_client2.exe\")\n";
   cout << "window.location.href=encodeURI(\""+CAS_SERVER_URL+"/login?service="+CAS_CLIENT_URL+"\")\n";
   cout << "</script>\n";
   cout << "</head>\n";
   cout << "</html>\n";
}
//解析xml数据
string tinyXml(string xml){
  const char * xmlString = xml.c_str();
  TiXmlDocument *doc = new TiXmlDocument();
  doc->Parse(xmlString);

  TiXmlElement * rootElement = doc->RootElement(); //serviceResponse元素
  TiXmlElement* authenElement = rootElement->FirstChildElement();  // authenticationSuccess元素  
  TiXmlElement* userElement = authenElement->FirstChildElement();  // user元素  
  const char * userName = userElement->GetText();
  string s(userName);
  return s;
}
//获取cookie
string geCookie(){
   Cgicc cgi;
   const_cookie_iterator cci;
   // 获取环境变量
   const CgiEnvironment& env = cgi.getEnvironment();
   string userName = "";
   for( cci = env.getCookieList().begin();
        cci != env.getCookieList().end(); 
        ++cci )
   {
      if(cci->getName()=="userName"){
        userName = cci->getValue();
      }
   }
   return userName;
}
//ticket校验的回调函数
size_t http_data_writer(void* data, size_t size, size_t nmemb, void* content)  
{  
    long totalSize = size*nmemb;  
    std::string* symbolBuffer = (std::string*)content;  
    if(symbolBuffer)  
    {  
        symbolBuffer->append((char *)data, ((char*)data)+totalSize);  
    }  
    return totalSize;  
}  

//ticket校验
int ticketViladate(string ticket)
{
    CURL *curl;             //定义CURL类型的指针
    CURLcode code;           //定义CURLcode类型的变量,保存返回状态码
    curl = curl_easy_init();        //初始化一个CURL类型的指针

    string userName = geCookie();

    if(curl!=NULL)
    {  
       // string url = "http://127.0.0.1:8080/cas_server/serviceValidate?ticket="+ticket+"&service=http://nagsh:8080/cas_cgi_client/cgi-bin/cas_client2.exe";
        string url = CAS_SERVER_URL+"/serviceValidate?ticket="+ticket+"&service="+CAS_CLIENT_URL;
        curl_easy_setopt(curl, CURLOPT_URL,url.data());   
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_data_writer);     //获取Response数据回调  
        std::string strData;                                          //获取的数据 正常代表的即为登录用户的用户名
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&strData);   //设置写数据  
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE);    //如果数字证书不完整时可设置为FALSE,不进行验证  
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, TRUE);        //捕获重定向URL的信息  
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); 
        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 20); 
        //调用curl_easy_perform 执行我们的设置.并进行相关的操作. 在这 里只在屏幕上显示出来.
        code = curl_easy_perform(curl);
        if(code == CURLcode::CURLE_OK)  
        {  
          long responseCode = 0;  
          curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);  
          if (responseCode < 200 || responseCode >= 300 || strData.empty())  
          {  
               toHomePage("响应码不正确");
                return 0;  
          }

          //跳转到客户端
          //解析出用户名
          strData = tinyXml(strData);
          //保存cookie然后重定向
          rediretToClient(strData);  

        }else{
            //
            toHomePage("解析失败,错误码:");
            cout <<code;
        }  

        //清除curl操作.
        curl_easy_cleanup(curl);
    }

    return 0; 
}
//检验请求中是否包含ticket
int checkTicket(){
   Cgicc cgi;
   string userName = geCookie();
   //cookie中有用户名,直接跳转到客户端页面
   if(userName!=""){
      toHomePage(userName);
      return 0;
   }
   //参数不为空 0:有参数  1:没有参数
   if(cgi.getElements().empty()==0){
       form_iterator ticket = cgi.getElement("ticket");

       if( !ticket->isEmpty() && ticket != (*cgi).end()) {  
             //ticket有值
             //验证ticket  
             ticketViladate(**ticket);

       }else{
            //tickt没有值 
              //跳转到CAS Server
               toServerPage();
       }

   }else{
        //没有ticket 
            //判断cookie中JSESSIONID与session中1是否一致
               //是 进入页面
              //否 跳转到CAS Server
              toServerPage();

   }

   return 0;
}


int  cas_client_init()
{ 
   loadInfo();
   checkTicket();
   system("PAUSE");
   return 0;
}

IV.main.cpp

#include "cas_client.h"
int main(){
  cas_client_init();
  return 0;
}

4.代码解释

我按照代码的流程来解释上边的代码。
1.main.cpp文件调用的方法是cas_client_init,首先会执行loadInfo方法。–>2
2.loadInfo方法主要是使用tinyXml读取config.xml文件,获取cas_server地址和客户端的地址,写这步的原因是方便正式使用后修改地址,而不必每次都重新编译。–>3
3.接下来执行checkTicket方法,会使用cgicc从cookie中获取用户信息(c++貌似不支持session,只好使用cookie来存储用户信息),如果userName不为空,即用户已登录,则直接调用toHomePage方法跳转到客户端页面,否则进行下一步–>4
4.cookie中没有用户信息,在判断是否存在ticket,如果不存在,则跳转到cas_server–>5,否则进行ticket验证–>6.
5.在server端登录成功后,地址跳转回客户端,重新从1开始执行,因为请求中有ticket,所以跳转到6进行ticket验证。
6.在ticketViladate方法中使用libcurl的向server发送请求进行ticket验证,并获取返回结果,使用tinyXml库进行解析,获取用户名。–>7
7.获取到用户名后,调用rediretToClient重定向到client地址,将用户信息写入cookie中(同时,重定向后,地址栏将不再显示ticket)

关于代码中的具体介绍,我都写在了注释中,大家可以仔细阅读。

尾言:
这是我的第一个c++程序,竟然不是HelloWorld。
这次开发,遇到了很多问题,比如libcurl在tomcat6下不兼容,弄了一天多,后来走投无路换了个tomcat就好了。又比如ticket验证的过程及获取用户名的方式,还是在耐心的看了源码后茅塞顿开。总之,自己又进步了一点点。

源码地址:源码

<script type="text/javascript"> $(function () { $('pre.prettyprint code').each(function () { var lines = $(this).text().split('\n').length; var $numbering = $('<ul/>').addClass('pre-numbering').hide(); $(this).addClass('has-numbering').parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($('<li/>').text(i)); }; $numbering.fadeIn(1700); }); }); </script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值