环境: zlib-1.2.8 openssl-1.0.1g curl-7.36
Author: Kagula
LastUpdateDate: 2016-05-09
阅读前提:CMake工具的基本使用、配置openssl-1.0.1g 开发环境
下载zlib-1.2.8.tar.gz并解压缩到" D:\SDK\zlib-1.2.8",使用CMake工具生成zlib.sln,在Visual Studio2013中打开并编译即可。
假设Open SSL已经安装到“D:\SDK\openssl-1.0.1g”,先设置下面的环境变量
OPENSSL_LIBRARIES=D:\SDK\openssl-1.0.1g\out32
OPENSSL_ROOT_DIR=D:\SDK\openssl-1.0.1g
从http://curl.haxx.se/下载curl-7.36.0.zip并解压缩到“D:\SDK\curl-7.36.0”启动CMake工具Configure,分别设置LIB_EAY_RELEASE和SSL_EAY_RELEASE变量为“D:\SDK\openssl-1.0.1g\out32\libeay32.lib”,“D:\SDK\openssl-1.0.1g\out32\ssleay32.lib”,产生sln文件后打开,为里面的curl工程项目添加“USE_MANUAL”宏定义,然后build里面的4个项目成功。
为项目添加链接库libcurl_imp.lib , 把libcurl.dll文件复制到C++项目路径下,否则程序运行会提示找不到动态链接库。
下面是HTTP/HTTPS客户端示例
Source.cpp如何调用的示例
#include <iostream> using namespace std; #include "HttpClient.h" //下面是测试代码 using namespace kagula::network; void TestHttp() { CHttpClient client; string url; string post; string cookie="cookie.txt"; string response; url = "http://www.baidu.com/"; int nR = client.Get(url, response); cout << "TestHttp GET=" << nR << endl; client.Post(url, post, cookie, response); cout << "TestHttp POST=" << nR << endl; } //Https单向认证 void TestHTTPS() { CHttpClient client; string url; string response; client.SetDebug(true); url = "https://lijun:8443/escortcashbox/main/aboutUs.do"; //第一步:从服务端的keystore中导出cer证书文件放在客户端中 //第二步:在客户端中使用openssl把cer证书文件转成pem文件 //最后:我们可以用pem文件验证服务端的真实性。 //第三个参数设置为pem文件,用来认证https服务端的真实性。 int nR = client.Get(url, response,"D:\\MyServerSecurity\\tomcat7.pem"); //错误的证书会返回CURLE_SSL_CACERT=60错误。 //若第三个参数为空,虽然也可以正常访问https服务,但是不验证https服务端的身份证实性。 //int nR = client.GetS(url, response); cout << "TestHttpClientGZip GET=" << nR << endl; } //Https双向认证 void TestHTTPS2() { CHttpClient client; string url; string response; client.SetDebug(true); url = "https://lijun:8443/escortcashbox/main/aboutUs.do"; //第一步:从服务端的keystore中导出cer证书文件放在客户端中 //第二步:在客户端中使用openssl把cer证书文件转成pem文件 //最后:我们可以用pem文件验证服务端的真实性。 //第三个参数设置为pem文件,用来认证https服务端的真实性。 //第四个参数设置为pem文件,用来认证https客户端的真实性,这个文件含公钥和私钥。 //第五个参数,存取客户端生成的pem文件所需的密码。 int nR = client.Get(url, response,"D:\\MyServerSecurity\\tomcat7.pem" ,"D:\\MyClientSecurity\\client_all.pem","123456"); //若第三个参数为空,虽然也可以正常访问https服务,但是不验证https服务端的身份证实性。 cout << "TestHttpClientGZip GET=" << nR << endl; } void TestCookie() { CHttpClient client; string url; string cookie="d:\\cookie.txt"; string post; string response; //login url = "https://lijun:8443/escortcashbox/main/login.do"; post="data={\"version\":\"1.0.0.0\",\"user\":\"admin\",\"password\":\"123\"}"; client.SetDebug(true); int nR = client.Post(url, post, cookie, response, "D:\\MyServerSecurity\\tomcat7.pem","D:\\MyClientSecurity\\client_all.pem","123456"); cout << "POST=" << nR << endl; cout << "Response=" << response << endl; //access url = "https://lijun:8443/escortcashbox/main/petroStationQuery.do"; post = "data={\"version\":\"1.0.0.0\",\"start\":\"1\";\"end\":\"9\",\"pagesize\":\"88\"}"; client.Post(url, post, cookie, response, "D:\\MyServerSecurity\\tomcat7.pem","D:\\MyClientSecurity\\client_all.pem","123456"); cout << "POST=" << nR << endl; cout << "Response=" << response << endl; //logout url = "https://lijun:8443/escortcashbox/main/logout.do"; post = ""; client.Post(url, post, cookie, response, "D:\\MyServerSecurity\\tomcat7.pem","D:\\MyClientSecurity\\client_all.pem","123456"); cout << "POST=" << nR << endl; cout << "Response=" << response << endl; //access url = "https://lijun:8443/escortcashbox/main/petroStationQuery.do"; post = "data={\"version\":\"1.0.0.0\",\"start\":\"1\";\"end\":\"9\",\"pagesize\":\"88\"}"; client.Post(url, post, cookie, response, "D:\\MyServerSecurity\\tomcat7.pem","D:\\MyClientSecurity\\client_all.pem","123456"); cout << "POST=" << nR << endl; cout << "Response=" << response << endl; } int main(int argc, char *argv[]) { //TestHttp(); //TestHTTPS(); //TestHTTPS2(); TestCookie(); cin.get(); return 0; }
HttpClient.h封装好的头文件
//HttpClient.h源代码清单 #ifndef _HTTPCLIENT_H_ #define _HTTPCLIENT_H_ #include <string> /* Title: Get Response from Web Server by HTTP/HTTPS method. Environment: Windows 8.1, Windows 10 Visual Studio 2013 Update1, Visual Studio 2013 Update5 libcurl 7.36.0, libcurl 7.46.0 Last Update: 2016-05-09 Remark: [1]如果要在多线程方式下同时调用多个CHttpClient实例, 需要在App初始化的时候调用kagula::network::Init(); 在App结束的时候调用kagula::network::Cleanup(); [2]编译libcurl必须打开zlib标志。 History: [1]转自csdn某个页面,对源代码做了小修改,添加了Cookie的支持. [2]添加了gzip encoding支持,如果服务器支持gzip,服务器会把数据以gzip方式压缩后返回, libcurl则会自动解压数据。 [3]删除一些重复的代码,修复同gcc不兼容的问题。 Reference: curl_eay_setopt manual http://www.helplib.net/s/linux.die/65_2740/man-3-curl-easy-setopt.shtml C++ cout format http://www.cnblogs.com/devymex/archive/2010/09/06/1818754.html */ namespace kagula { namespace network { void Init(); void Cleanup(); class CHttpClient { public: CHttpClient(void); ~CHttpClient(void); public: /** * @brief HTTP/HTTPS POST请求 * @param strUrl 输入参数,请求的Url地址,如:https://www.alipay.com * @param strPost 输入参数,使用如下格式para1=val1¶2=val2&… * @param strCookie 输入参数,Cookie文件名,例如 d:\temp\cookie.txt * 如果为空,不启用Cookie. * @param strResponse 输出参数,返回的内容 * @param pCaPath 输入参数,为CA证书的路径.如果输入为NULL,则不验证服务器端证书的有效性. * @param pClientCalPath 输入参数,为客户端证书的路径.如果输入为NULL,则不验证客户端证书的有效性. * @param pClientCalPassword 输入参数,为客户端证书的存取密码. * @return 返回是否Post成功 * 0 成功 * 7 无法连接 * 58 服务端验证客户端证书失败。 * 60 客户端验证服务端证书失败。 */ int Post(const std::string & strUrl, const std::string & strPost, const std::string& strCookie, std::string & strResponse, const char * pCaPath = NULL, const char * pClientCalPath = NULL,const char * pClientCalPassword = NULL); /** * @brief HTTP/HTTPS GET请求 * @param strUrl 输入参数,请求的Url地址,如:https://www.alipay.com * @param strResponse 输出参数,返回的内容 * @param pCaPath 输入参数,为CA证书的路径.如果输入为NULL,则不验证服务器端证书的有效性. * @param pClientCalPath 输入参数,为客户端证书的路径.如果输入为NULL,则不验证客户端证书的有效性. * @param pClientCalPassword 输入参数,为客户端证书的存取密码. * @return 返回是否Get成功 */ int Get(const std::string & strUrl, std::string & strResponse, const char * pCaPath = NULL, const char * pClientCalPath = NULL,const char * pClientCalPassword = NULL); public: void SetDebug(bool bDebug); private: bool m_bDebug; bool PrintCookies(void* curl, std::string& strOut); }; } } #endif
源文件清单
//HttpClient.cpp源代码清单 #include "HttpClient.h" #include <iostream> #include <curl/curl.h> #include <iomanip> #include <sstream> #ifdef WIN32 #pragma comment(lib,"libcurl_imp.lib") #endif namespace kagula { namespace network { CHttpClient::CHttpClient(void) : m_bDebug(false) { } CHttpClient::~CHttpClient(void) { } bool CHttpClient::PrintCookies(void* curl, std::string& strOut) { std::ostringstream ostr; CURLcode res; struct curl_slist *cookies; res = curl_easy_getinfo(curl, CURLINFO_COOKIELIST, &cookies); if (res != CURLE_OK) { ostr << "Curl curl_easy_getinfo failed:" << curl_easy_strerror(res) << std::endl; strOut = ostr.str(); return false; } const struct curl_slist *nc = cookies; int i = 1; ostr << "Cookies, curl knows:" << std::endl; while (nc) { ostr << "[" << i++ << "]: " << nc->data << std::endl; nc = nc->next; } return true; } static int OnDebug(CURL *, curl_infotype itype, char * pData, size_t size, void *) { if (itype == CURLINFO_TEXT) { //printf("[TEXT]%s\n", pData); } else if (itype == CURLINFO_HEADER_IN) { printf("[HEADER_IN]%s\n", pData); } else if (itype == CURLINFO_HEADER_OUT) { printf("[HEADER_OUT]%s\n", pData); } else if (itype == CURLINFO_DATA_IN) { printf("[DATA_IN]%s\n", pData); } else if (itype == CURLINFO_DATA_OUT) { printf("[DATA_OUT]%s\n", pData); } return 0; } size_t OnWriteData(void* buffer, size_t size, size_t nmemb, void* lpVoid) { std::string* str = reinterpret_cast<std::string*>(lpVoid); if (NULL == str || NULL == buffer) { return -1; } char* pData = reinterpret_cast<char*>(buffer); str->append(pData, size * nmemb); return nmemb; } int CHttpClient::Post(const std::string & strUrl, const std::string & strPost, const std::string& strCookie, std::string & strResponse, const char * pCaPath,const char * pClientCalPath,const char * pClientCalPassword) { strResponse = ""; CURLcode res; CURL* curl = curl_easy_init(); if (NULL == curl) { return CURLE_FAILED_INIT; } if (m_bDebug) { curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, OnDebug); } curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str()); curl_easy_setopt(curl, CURLOPT_POST, 1); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, strPost.c_str()); curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&strResponse); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); if (strCookie.length()>0) { curl_easy_setopt(curl, CURLOPT_COOKIEFILE, (void *)strCookie.c_str()); curl_easy_setopt(curl, CURLOPT_COOKIEJAR, (void *)strCookie.c_str()); } if (NULL == pCaPath) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); //需要在编译curl的时候,一同编译zlib标志。要不然找不到这个标志. //当然前提是你已经编译完成zlib. //发出接受gzip压缩内容的请求,如果服务器支持gzip内容,会返回压缩后的数据。 //如果Http服务器不支持gzip encoding也不影响libcurl正常工作。 //接受数据的时候,如果返回的是压缩数据,libcurl会自动解压数据。 curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip"); } else { //缺省情况就是PEM,所以无需设置,另外支持DER //curl_easy_setopt(curl,CURLOPT_SSLCERTTYPE,"PEM"); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true); curl_easy_setopt(curl, CURLOPT_CAINFO, pCaPath); //服务端需要认证客户端的真实性,即双向认证。 if(pClientCalPath!=NULL) { curl_easy_setopt(curl,CURLOPT_SSLCERT, pClientCalPath); curl_easy_setopt(curl,CURLOPT_SSLCERTPASSWD, pClientCalPassword); curl_easy_setopt(curl,CURLOPT_SSLCERTTYPE, "PEM"); curl_easy_setopt(curl,CURLOPT_SSLKEY, pClientCalPath); curl_easy_setopt(curl,CURLOPT_SSLKEYPASSWD, pClientCalPassword); curl_easy_setopt(curl,CURLOPT_SSLKEYTYPE, "PEM"); } } // curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3); //Web服务器一般会重定向链接,比如访问http:/xxx/x1.do自动转到http:/xxx/x2.do //所以一定要设置CURLOPT_FOLLOWLOCATION为1,否则重定向后的数据不会返回。 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION,1); res = curl_easy_perform(curl); curl_easy_cleanup(curl); return res; } int CHttpClient::Get(const std::string & strUrl, std::string & strResponse, const char * pCaPath ,const char * pClientCalPath,const char * pClientCalPassword) { CURLcode res; CURL* curl = curl_easy_init(); if (NULL == curl) { return CURLE_FAILED_INIT; } if (m_bDebug) { curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, OnDebug); } curl_easy_setopt(curl, CURLOPT_URL, strUrl.c_str()); curl_easy_setopt(curl, CURLOPT_READFUNCTION, NULL); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, OnWriteData); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&strResponse); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); if (NULL == pCaPath) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); //需要在编译curl的时候,一同编译zlib标志。要不然找不到这个标志. //当然前提是你已经编译完成zlib. //发出接受gzip压缩内容的请求,如果服务器支持gzip内容,会返回压缩后的数据。 //如果Http服务器不支持gzip encoding也不影响libcurl正常工作。 //接受数据的时候,如果返回的是压缩数据,libcurl会自动解压数据。 curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip"); //gzip会让https受到安全攻击 //参考 //《Can you use gzip over SSL? And Connection: Keep-Alive headers》 //http://stackoverflow.com/questions/2767211/can-you-use-gzip-over-ssl-and-connection-keep-alive-headers } else { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true); curl_easy_setopt(curl, CURLOPT_CAINFO, pCaPath); //检查访问的url(domain)是否和证书中的url(domain)一致 //CURLOPT_SSL_VERIFYHOST缺省为1,所以无需设置 //curl_easy_setopt(curl,CURLOPT_SSL_VERIFYHOST,1); //服务端需要认证客户端的真实性,即双向认证。 if(pClientCalPath!=NULL) { curl_easy_setopt(curl,CURLOPT_SSLCERT, pClientCalPath); curl_easy_setopt(curl,CURLOPT_SSLCERTPASSWD, pClientCalPassword); curl_easy_setopt(curl,CURLOPT_SSLCERTTYPE, "PEM"); curl_easy_setopt(curl,CURLOPT_SSLKEY, pClientCalPath); curl_easy_setopt(curl,CURLOPT_SSLKEYPASSWD, pClientCalPassword); curl_easy_setopt(curl,CURLOPT_SSLKEYTYPE, "PEM"); } } curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3); //Web服务器一般会重定向链接,比如访问http:/xxx/x1.do自动转到http:/xxx/x2.do //所以一定要设置CURLOPT_FOLLOWLOCATION为1,否则重定向后的数据不会返回。 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION,1); res = curl_easy_perform(curl); curl_easy_cleanup(curl); return res; } /////////////////////////////////////////////////////////////////////////////////////////////// void CHttpClient::SetDebug(bool bDebug) { m_bDebug = bDebug; } void Init() { //the function is not thread safe. curl_global_init(CURL_GLOBAL_ALL); } void Cleanup() { curl_global_cleanup(); } } }
注意:
[1]MSys64 Mingw32 QtCreator 32位C++程序 配置libcurl
假设Msys64是安装在C盘默认路径上
第一步:在Msys64中查询可用的curl包
pacman -Ss curl
第二步:安装
pacman -S mingw-w64-i686-curl
第三步:在.pro文件下加入下面的代码
LIBS += C:\msys64\mingw32\lib\libcurl.dll.a
INCLUDEPATH += C:\msys64\mingw32\include
最后:编译链接依赖libcurl的项目成功。
https原理及tomcat配置https方法
http://jingyan.baidu.com/article/a948d6515d3e850a2dcd2ee6.html
[3]单向认证,让客户端信任服务端证书
第一步:
双击从服务端keystore中导出的cer文件可以导入证书。
windows下“certmgr.msc”命令可以进入证书管理。
自己制作的证书导入到Win7后默认在“中级证书颁发机构”->“证书”节点里。
第二步:
需要把你做的证书拖到“受信任的根证书颁发机构”->“证书”节点中去,否则
浏览器会提醒“此网站出具的安全证书不是由受信任的证书颁发机构颁发的”等类似错误。
chrome需要重启,IE直接刷新页面,就不会出现警告了。
注意:
数字证书转换cer---pem
在Msys2 Shell中确保安装好openssl.
在Msys2 Shell中使用openssl命令后进入openssl提示符,输入下面的命令
x509 -inform der -in d:/MyServerSecurity/tomcat7.cer -out d:/MyServerSecurity/tomcat7.pem
[4]双向认证,让服务端信任客户端的证书
相当于“Https单向认证”,添加了“服务端验证客户端身份真实性”的动作。
需要注意的是:服务端的密钥库参数“CN”必须与服务端的IP地址相同,否则会报错,客户端的任意。
如果服务端的CN为lijun,则客户端不能为lijun,即这两个不能是同一个名字。
第一步:客户端生成证书(用于服务端验证客户端)
keytool -validity 365 -genkeypair -v -alias kagula -keyalg RSA -storetype PKCS12 -keystore D:\MyClientSecurity\kagulakey.p12 -storepass 123456 -keypass 123456
kagula为证书的名称,D:\MyClientSecurity\kagulakey.p12证书的存放位置。
第二步:证书格式转为cer文件。
keytool -export -v -alias kagula -keystore D:\MyClientSecurity\kagulakey.p12 -storetype PKCS12 -storepass 123456 -rfc -file D:\MyClientSecurity\kagulakey.cer
kagula为证书的名称,D:\MyClientSecurity\kagulakey.p12证书的存放位置,123456证书密码,D:\MyClientSecurity\kagulakey.cer导出的文件。
第三步:添加到(或新建)一个keystore文件
keytool -import -v -alias kagula -file D:\MyClientSecurity\kagulakey.cer -keystore D:\MyServerSecurity\tomcat7_client.keystore -storepass 123456
tomcat7_client.keystore如果不存在就会新建一个keystore,如果存在会添加到已经存在的keystore中。
第四步:查看keystore文件中的内容
keytool -list -keystore D:\MyServerSecurity\tomcat7_client.keystore
第五步:修改tomcat7中的server.xml文件
原单向认证的配置如下
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="D:\\MyServerSecurity\\tomcat7.keystore" keystorePass="123456"/>
现在修改后,如下
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="true" sslProtocol="TLS"
keystoreFile="D:\\MyServerSecurity\\tomcat7.keystore" keystorePass="123456"
truststoreFile="D:\\MyServerSecurity\\tomcat7_client.keystore" truststorePass="123456"/>
添加了最后一行属性使它指向客户端证书,并把clientAuth属性从false改为true。
第六步(可选):
Step6-1:
测试Windows下的Chrome是否还能访问服务器,果然刷新浏览器后
“https://lijun:8443/escortcashbox/main/aboutUs.do”返回错误信息
Step6-2:
测试libcurl是否还能访问服务器,现在libcurl返回7(无法连接服务器)的错误信息。
最后一步-让Windows下的Chrome和IE能访问服务端:
双击D:\MyClientSecurity\kagulakey.p12文件,“不需要添加到受信任的根证书机构”结点,直接导入证书即可。
默认在certmgr.msc命令,“个人”->“证书”节点下。
最后一步-让libcurl能访问服务端:
使用msys64打开openssl命令行工具
#客户端个人证书的公钥
openssl>pkcs12 -in D:\MyClientSecurity\myclientkey.p12 -out D:\MyClientSecurity\client_publickey.pem -nokeys
也许如果在当前command shell下能找到openssl.exe也可以用下面的命令
“openssl pkcs12 -in D:\MyClientSecurity\myclientkey.p12 -out D:\MyClientSecurity\client_publickey.pem -nokeys”
#客户端个人证书的私钥
openssl pkcs12 -in D:\MyClientSecurity\myclientkey.p12 -out D:\MyClientSecurity\client_privatekey.pem -nocerts -nodes
#也可以转换为公钥与私钥合二为一的文件; 客户端公钥与私钥,一起存在all.pem中
openssl>pkcs12 -in D:\MyClientSecurity\kagulakey.p12 -out D:\MyClientSecurity\client_all.pem -nodes
1、使用client_publickey.pem + client_privatekey.pem (未在win console shell下测试)
curl -k --cert client_publickey.pem --key D:\MyClientSecurity\client_privatekey.pem https://lijun:8443/escortcashbox/main/aboutUs.do
2、可以在Msys64 Shell中执行curl命令(如果已经安装了curl)...使用all.pem
curl -k --cert /d/MyClientSecurity/client_all.pem https://lijun:8443/escortcashbox/main/aboutUs.do
下面是双向认证的参考资料
SSL——Secure Sockets Layer
http://www.blogjava.net/icewee/archive/2012/06/04/379947.html
使用libcurl实现上传文件到FTP服务器
http://blog.csdn.net/lee353086/article/details/5823145
使用libcurl下载http://www.baidu.com/img/baidu.gif示例
http://www.cppblog.com/qiujian5628/archive/2008/06/28/54873.html
libcurl的使用总结(一)
http://www.vimer.cn/2010/03/libcurl%E7%9A%84%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93%EF%BC%88%E4%B8%80%EF%BC%89.html
PHP的curl实现get,post 和 cookie
http://www.tsingpost.com/articles/201403/525.html