使用到的类库
jsoncpp:用于json数据操作
boost:智能指针和正则表达,如果是C++11以上版本可以替换成C++11的智能指针和正则表达式
CryptoPP:大名鼎鼎的免费的C++加解密类库,Crypto++几乎涵盖了所有的加解密算法
以上类库是可以跨平台使用
代码
宏定义请参看负载均衡权重系数算法 C++中的se_std.h
头文件
#include <string>
#include <strstream>
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/regex.hpp>
#include <json/json.h>
#include "se_std.h"
#include "se_conf.h"
#include <base64.h>
#include <hmac.h>
#include <sha.h>
using namespace std;
#ifndef H_3695350A_4107_49D7_BE41_60F19549BD53
#define H_3695350A_4107_49D7_BE41_60F19549BD53
/*
* Author:
* Version:1.0
* Copyright:
* Description: 生成token和验证token
* https://jwt.io/introduction/
* Create:2018-02-01
* LastChange:2018-02-01
*/
class se_token : public boost::noncopyable {
private:
const int FLAG;
CryptoPP::HMAC< CryptoPP::SHA256 > m_hs256;
boost::scoped_ptr<Json::StreamWriter> m_jwrite;
boost::scoped_ptr<Json::CharReader> m_jread;
stringstream m_strbuf;
string m_header, m_header_base64;
Json::Value m_jheader;
boost::regex m_reg_exp;
char m_numbuf[SE_MAX_NUMSTR];
public:
se_token()
:FLAG(CryptoPP::HashVerificationFilter::PUT_RESULT | CryptoPP::HashVerificationFilter::HASH_AT_END) {
this->init();
};
se_token(const CryptoPP::SecByteBlock &key)
:m_hs256(key, key.size()),
FLAG(CryptoPP::HashVerificationFilter::PUT_RESULT | CryptoPP::HashVerificationFilter::HASH_AT_END) {
this->init();
};
virtual ~se_token() {};
private:
/*初始化*/
void init();
/*清除字符缓冲区内容*/
inline void clear(stringstream &strbuf) const {
strbuf.str(""); strbuf.clear();
}
private:
void parse_token(const string &token, string &msg, string &b64payload, string &b64sig) const;
public:
/*设置HMAC key*/
inline void set_key(const CryptoPP::SecByteBlock &key) {
this->m_hs256.SetKey(key, key.size());
}
/*设置HMAC key(key为base64编码)*/
void set_key_base64(const string &b64key);
/*生成key*/
void generate_key(string &b64key) const;
/*生成签名*/
void signature(const string &plain, string &mac);
/*生成签名(签名为base64编码)*/
void signature_base64(const string &plain, string &b64);
/*验证签名*/
bool verify(const string &msg, const string &signature);
/*验证签名(签名为base64编码)*/
bool verify_base64(const string &msg, const string &b64);
/*获取JWT Header信息*/
inline const string &header() const {
return this->m_header;
}
/*获取JWT Header信息*/
inline const Json::Value &header_json() const {
return this->m_jheader;
}
/* 根据json生成token
* 注意,生成的token如要附件在url中必须采用url_encode后输出
*/
void generate_token(const Json::Value &payload, string &token);
/* 根据payload json格式字符串生成token
* 注意,生成的token如要附件在url中必须采用url_encode后输出
*/
void generate_token(const string &payload, string &token);
/*解析token*/
void parse_token(const string &token, Json::Value &payload);
/*更新token的过期时间*/
void update_expires(const se_conf_server &conf, string &token);
};
#endif /* H_3695350A_4107_49D7_BE41_60F19549BD53 */
源文件
#include "se_token.h"
#include <osrng.h>
#define HS256KEY ("eoRK6DnPRTbQ72oEG+ANFg==")
void se_token::init() {
Json::StreamWriterBuilder writer;
writer["commentStyle"] = "None";
writer["indentation"] = ""; // or whatever you like
writer["precision"] = 6;
this->m_jwrite.reset(writer.newStreamWriter());
Json::CharReaderBuilder rbuilder;
this->m_jread.reset(rbuilder.newCharReader());
this->m_header = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}";
CryptoPP::StringSource(this->m_header, true,
new CryptoPP::Base64Encoder(
new CryptoPP::StringSink(this->m_header_base64), false
)
);
this->m_reg_exp = "(\\\"exp\\\")\\:(\\d+)";
this->m_jheader["typ"] = "JWT";
this->m_jheader["alg"] = "HS256";
this->set_key_base64(HS256KEY);
}
void se_token::generate_key(string &b64key) const {
CryptoPP::AutoSeededRandomPool prng;
CryptoPP::SecByteBlock key(16);
prng.GenerateBlock(key, key.size());
CryptoPP::StringSource(key, key.size(), true,
new CryptoPP::Base64Encoder(
new CryptoPP::StringSink(b64key), false
)
);
}
void se_token::set_key_base64(const string &b64key) {
CryptoPP::SecByteBlock key;
string encoded;
CryptoPP::StringSource(b64key, true,
new CryptoPP::Base64Decoder(
new CryptoPP::StringSink(encoded)
)
);
key.Assign((const byte *)encoded.c_str(), encoded.size());
this->set_key(key);
}
void se_token::signature(const string &plain, string &mac) {
mac.clear();
CryptoPP::StringSource ss1(plain, true,
new CryptoPP::HashFilter(this->m_hs256,
new CryptoPP::StringSink(mac)
)
);
}
void se_token::signature_base64(const string &plain, string &b64) {
string mac;
b64.clear();
this->signature(plain, mac);
CryptoPP::StringSource(mac, true,
new CryptoPP::Base64Encoder(
new CryptoPP::StringSink(b64), false
)
);
}
bool se_token::verify(const string &msg, const string &signature) {
bool result = false;
CryptoPP::StringSource ss(msg + signature, true,
new CryptoPP::HashVerificationFilter(this->m_hs256,
new CryptoPP::ArraySink((byte*)&result, sizeof(result)),
FLAG
)
);
return result;
}
bool se_token::verify_base64(const string &msg, const string &b64sig) {
string signature;
CryptoPP::StringSource(b64sig, true,
new CryptoPP::Base64Decoder(
new CryptoPP::StringSink(signature)
)
);
return this->verify(msg, signature);
}
void se_token::generate_token(const Json::Value &payload, string &token) {
this->clear(this->m_strbuf);
ostream stream(this->m_strbuf.rdbuf());
this->m_jwrite->write(payload, &stream);
string str_payload = this->m_strbuf.str();
this->clear(this->m_strbuf);
string payload_base64, signature;
CryptoPP::StringSource(str_payload, true,
new CryptoPP::Base64Encoder(
new CryptoPP::StringSink(payload_base64), false
)
);
token.clear();
token.append(this->m_header_base64).append(".").append(payload_base64);
this->signature_base64(token, signature);
token.append(".").append(signature);
}
void se_token::generate_token(const string &payload, string &token) {
string payload_base64, signature;
CryptoPP::StringSource(payload, true,
new CryptoPP::Base64Encoder(
new CryptoPP::StringSink(payload_base64), false
)
);
token.clear();
token.append(this->m_header_base64).append(".").append(payload_base64);
this->signature_base64(token, signature);
token.append(".").append(signature);
}
void se_token::parse_token(const string &token, string &msg, string &b64payload, string &b64sig) const {
string::size_type pos1 = token.find(".");
string::size_type pos2 = token.find(".", pos1 + 1);
if (string::npos == pos1 || string::npos == pos2)
SE_THROW("invalid token!");
msg = token.substr(0, pos2);
++pos1;
b64payload = token.substr(pos1, pos2 - pos1);
++pos2;
b64sig = token.substr(pos2);
}
void se_token::parse_token(const string &token, Json::Value &payload) {
string msg, b64payload, b64sig;
this->parse_token(token, msg, b64payload, b64sig);
if (!this->verify_base64(msg, b64sig))
SE_THROW("lllegal token!");
string plian, tmp;
CryptoPP::StringSource(b64payload, true,
new CryptoPP::Base64Decoder(
new CryptoPP::StringSink(plian)
)
);
const char *ptr_start = plian.c_str();
const char *ptr_end = ptr_start + plian.size();
bool parsingSuccessful = this->m_jread->parse(ptr_start, ptr_end, &payload, &tmp);
if (!parsingSuccessful)
SE_THROW(tmp);
}
void se_token::update_expires(const se_conf_server &conf, string &token) {
string msg, b64payload, b64sig;
this->parse_token(token, msg, b64payload, b64sig);
if (!this->verify_base64(msg, b64sig))
SE_THROW("invalid token!");
string plian;
CryptoPP::StringSource(b64payload, true,
new CryptoPP::Base64Decoder(
new CryptoPP::StringSink(plian)
)
);
/*获取当前时间*/
const time_t tt = time(NULL);
time_t exp = tt + conf.expires();
#if defined(_MSC_VER)
_snprintf_s(this->m_numbuf, SE_MAX_NUMSTR, "$1:%I64d", exp);
#else
snprintf(this->m_numbuf, SE_MAX_NUMSTR, "$1:%lld", exp);
#endif
//替换过期时间
std::string result = boost::regex_replace(plian, this->m_reg_exp, this->m_numbuf);
this->generate_token(result, token);
}
输出
调用generate_token方法后的输出结果(JWT)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MTc0OTgwNzQsImlzcyI6ImttY2IuY29tIiwibmFtZSI6InVzZXJpZCIsInVzZXJpZCI6MX0=.u3lauh/Inw+fTq7kITL+5mwQQ65LP5t2g9CHXWQZkrk=
如果要直接在url中使用,还需要对上述字符串进行url编码,否则因url编码的原因,会导致验证失败.
/*经过url编码后的token,验证成功*/
http://localhost:9101/heightservice?m=chatoken&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MTc0OTk5ODAsImlzcyI6ImttY2IuY29tIiwibmFtZSI6InVzZXJpZCIsInVzZXJpZCI6MX0%3D.EGK4P7wqMgm0HjwLuApkqVvyc5AZUJ6zvnQsBrqlhHM%3D
2018-11-30补充
根据JWT的标准,token Base64还需要用下面两个函数转换.转换后url就没问题了,同时可以在JWT网站上解析token
void SE_base64uri_encode(char *b64, int32_t len) {
int i, t;
for (i = t = 0; i < len; i++) {
switch (b64[i]) {
case '+':
b64[t++] = '-';
break;
case '/':
b64[t++] = '_';
break;
case '=':
break;
default:
b64[t++] = b64[i];
}
}
b64[t] = '\0';
}
bool SE_base64uri_decode(const char *src, int32_t len, char **base64) {
int32_t i = 0, z = 0;
char *bin_data = NULL;
bin_data = malloc(len + 4);
if (SE_PTR_ISNULL(bin_data))
goto SE_ERROR_CLEAR;
for (i = 0; i < len; i++) {
switch (src[i]) {
case '-':
bin_data[i] = '+';
break;
case '_':
bin_data[i] = '/';
break;
default:
bin_data[i] = src[i];
}
}
z = 4 - (i % 4);
if (z < 4) {
while (z--)
bin_data[i++] = '=';
}
bin_data[i] = '\0';
*base64 = bin_data;
return true;
SE_ERROR_CLEAR:
SE_free(bin_data);
return false;
}