最近需要使用C++服务中根据用户名生成全局服务的token,然后在java服务中印证token正确性。因为java服务中使用jwt库生成全局的token, 我就对应找C++中jwt的库, github中有开源库叫jwt-cpp然后我就下载了, 我使用没有找到java中生成token好使用的接口、没有办法我只有看一下jwt生成token的步骤实现一遍
HMACSHA512(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-512-bit-secret
) secret base64 encoded
jwt由三部分构成
头部json格式
{"typ“: "JWT", "alg":"HS512"}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
标准中注册的声明 (建议但不强制使用) :
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
定义一个payload:
{ "sub": "1234567890", "name": "John Doe", "admin": true}
然后将其进行base64加密,得到Jwt的第二部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串(头部在前),然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q
密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以需要保护好。
最后一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输入产生的输出总是不一样的。对于两个不同的输入,产生同样的输出的概率极其地小(有可能比我成世界首富的概率还小)。所以,我们就把“不一样的输入产生不一样的输出”当做必然事件来看待吧。
所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。
服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名。那么服务器应用是怎么知道我们用的是哪一种算法呢?别忘了,我们在JWT的头部中已经用alg字段指明了我们的加密算法了。
如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/hmac.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <iostream>
namespace chen {
namespace jwt {
//java 从最后 删除'='个数 url-unfriendly base64 char u
int remove_padding( char* data, size_t len )
{
int paddingCount = 0;
//int encoded_payload_len = strlen(encoded_payload);
for (int i = len - 1; i > 0; --i)
{
if (data[i] == '=')
{
++paddingCount;
}
else
{
break;
}
}
//replace URL-unfriendly Base64 chars to url-friendly ones:
for (int i = 0; i < len; ++i)
{
if (data[i] == '+')
{
data[i] = '-';
}
else if (data[i] == '/')
{
data[i] = '_';
}
}
return len - paddingCount;
}
std::string create_token( const std::string& secret_key, const std::string& payload)
{
// 构建头部
static const char* header = "{\"alg\":\"HS512\"}";
char buffer[10240] = {0};
// 计算 Base64 编码后头部的长度
int header_len = EVP_ENCODE_LENGTH(strlen(header));
// 计算 Base64 编码后负载的长度
int payload_len = payload.length();// = EVP_ENCODE_LENGTH(strlen(payload));
// 分配内存存储 Base64 编码后的头部和负载
char encoded_header[4096] = {0};
char encoded_payload[4096] = {0};
// Base64 编码头部和负载
EVP_EncodeBlock((unsigned char*)encoded_header , (const unsigned char*)header, strlen(header));
EVP_EncodeBlock((unsigned char*)encoded_payload , (const unsigned char*)payload.c_str(), payload.length());
// 构建待签名字符串
int unsigned_data_len = remove_padding( encoded_header , strlen(encoded_header) ) + remove_padding(encoded_payload, strlen(encoded_payload) ) + 2; // 加上两个点号
snprintf(buffer, unsigned_data_len, "%s.%s", std::string(encoded_header, remove_padding((char*)encoded_header , strlen(encoded_header) )).c_str(), std::string(encoded_payload, remove_padding((char*)encoded_payload , strlen(encoded_payload))).c_str());
// 使用 HMAC SHA-512 对待签名字符串进行签名
signed char hmac_result[EVP_MAX_MD_SIZE];// = { 0 };
unsigned int hmac_len;// = 0;
HMAC(EVP_sha512(), secret_key.c_str(), secret_key.length(), (const unsigned char*)buffer, strlen(buffer), (unsigned char*)hmac_result, &hmac_len);
// Base64 编码签名结果
int signature_len = EVP_ENCODE_LENGTH(hmac_len);
char encoded_signature[4096 * 2] = {0};
EVP_EncodeBlock((unsigned char*)encoded_signature , (unsigned char*)hmac_result, hmac_len);
// 构建 JWT token
int token_len = unsigned_data_len + remove_padding((char *)encoded_signature , strlen(encoded_signature) ) + 1; // 加上两个点号和结尾的 null 字符
snprintf(buffer, token_len, "%s.%s.%s", std::string(encoded_header, remove_padding((char*)encoded_header, strlen(encoded_header))).c_str(), std::string(encoded_payload, remove_padding((char*)encoded_payload, strlen(encoded_payload))).c_str(), std::string(encoded_signature, remove_padding((char*)encoded_signature, strlen(encoded_signature))).c_str());
return buffer;
}
void test_create_token()
{
std::string key = "chensong";
std::string payload = "{\"chensong\":\"{\\\"userId\\\":18}\",\"sub\":\"chensong\"}";
std::string token = create_token(key, payload);
std::cout << "jwt token = " << token << std::endl;
}
}
}
JWT的HS512算法实现源码地址: