JWT(JSON Web Token):是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为Json对象安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用HMAC SHA256或RSA等对JWT进行签名。
JWT的组成:它是一个很长的字符串,中间用点(.)分隔成三个部分。它的三个部分依次是:Header(头部)、Payload(载荷)、Signature(签名)。JWT默认是不加密的。
Header:是一个Json对象,描述JWT的元数据,例子如下:alg属性表示签名的算法,默认是HMAC SHA256,写成HS256,也可使用RSA;typ属性表示这个令牌(token)的类型,JWT令牌统一写为JWT;id属性是用户自定义的。最后将此Json对象使用base64url编码成字符串。
{
"alg": "HS256",
"typ": "JWT",
"id": "fengbingchun"
}
Payload:也是一个Json对象,用来存放实际需要传递的数据。JWT规定了7个官方字段,供选用:iss(Issuer):签发人;exp(Expiration Time):过期时间;sub(Subject):主题;aud(Audience):受众;nbf(Not Before):生效时间;iat(Issued At):签发时间;jti(JWT ID):编号。除了官方字段,你还可以在这个部分定义私有字段。最后此Json对象也要使用base64url编码成字符串。例子如下:
{
"csdn": "https://blog.csdn.net/fengbingchun",
"github": "https://github.com//fengbingchun"
}
Signature:是对前两部分的签名,防止数据篡改。首先需要指定一个密钥(secret),然后使用Header里面指定的签名算法(默认是HMAC SHA256),按照以下的方式产生签名:算出签名后,也需要把此签名通过base64url编码成字符串。最后把Header、Payload、Signature三个部分编码成的字符串拼成一个字符串,每个部分之间用”点”(.)分隔,形式如xxxx.yyyy.zzzz。
HMACSHA256(base64urlEncode(header) + "." + base64urlEncode(payload), secret)
base64url和base64区别:base64有三个字符+、/和=,在URL里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_,这就是base64url。
一般此JWT会放在HTTP请求的头信息Authorization字段里:
Authorization: Bearer
注:以上内容主要来自网络整理,主要参考:
1. https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
2. https://jwt.io/introduction/
3. https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32
以下是代码实现:
Header和Payload内容如上所示,secret值为:"1234567890+-!@#$%^&*x()_=QF{>?",代码段如下:
int test_jwt()
{
// encode header
const char* header = "{\"alg\":\"HS256\",\"typ\":\"JWT\",\"id\":\"fengbingchun\"}";
int length_header = strlen(header);
int length_encoded_header = (length_header + 2) / 3 * 4;
std::unique_ptr encoded_header(new char[length_encoded_header]);
int ret = base64url_encode((const unsigned char*)header, length_header, encoded_header.get());
if (ret != BASE64_OK) {
fprintf(stderr, "fail to encode header: %s\n", header);
return -1;
}
fprintf(stdout, "encoded header: %s\n", encoded_header.get());
// encode payload
const char* payload = "{\"csdn\":\"https://blog.csdn.net/fengbingchun\",\"github\":\"https://github.com//fengbingchun\"}";
int length_payload = strlen(payload);
int length_encoded_payload = (length_payload + 2) / 3 * 4;
std::unique_ptr encoded_payload(new char[length_encoded_payload]);
ret = base64url_encode((const unsigned char*)payload, length_payload, encoded_payload.get());
if (ret != BASE64_OK) {
fprintf(stderr, "fail to encode payload: %s\n", payload);
return -1;
}
fprintf(stdout, "encoded payload: %s\n", encoded_payload.get());
// signature
std::string buffer;
buffer.append(encoded_header.get(), strlen(encoded_header.get()));
buffer.append(".");
buffer.append(encoded_payload.get(), strlen(encoded_payload.get()));
//const unsigned char key[] = { // 32 bytes
// 0xee, 0xbc, 0x1f, 0x57, 0x48, 0x7f, 0x51, 0x92, 0x1c, 0x04, 0x65, 0x66,
// 0x5f, 0x8a, 0xe6, 0xd1, 0x65, 0x8b, 0xb2, 0x6d, 0xe6, 0xf8, 0xa0, 0x69,
// 0xa3, 0x52, 0x02, 0x93, 0xa5, 0x72, 0x07, 0x8f };
const char key[] = { // 32 bytes
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '+', '-',
'!', '@', '#', '$', '%', '^', '&', '*', 'x', '(', ')', '_',
'=', 'Q', 'F', '{', '>', '<', '/', '?' };
std::unique_ptr signature(new unsigned char[EVP_MAX_MD_SIZE]);
HMAC_CTX* ctx = HMAC_CTX_new();
HMAC_CTX_reset(ctx);
const EVP_MD* engine = EVP_sha256();
unsigned int length_signature;
HMAC_Init_ex(ctx, key, sizeof(key), engine, nullptr);
HMAC_Update(ctx, reinterpret_cast(buffer.c_str()), buffer.length());
HMAC_Final(ctx, signature.get(), &length_signature);
HMAC_CTX_free(ctx);
// encode signature
int length_encoded_signature = (length_signature + 2) / 3 * 4;
std::unique_ptr encoded_signature(new char[length_encoded_signature]);
ret = base64url_encode(signature.get(), length_signature, encoded_signature.get());
if (ret != BASE64_OK) {
fprintf(stderr, "fail to encode signature\n");
return -1;
}
fprintf(stdout, "encoded signature: %s\n", encoded_signature.get());
buffer.append(".");
buffer.append(encoded_signature.get(), strlen(encoded_signature.get()));
fprintf(stdout, "jwt result: %s\n", buffer.c_str());
return 0;
}
上面的HMAC-SHA256是调用OpenSSL的接口实现的,也可调用bearssl接口实现,代码段如下:
br_hmac_key_context key_ctx;
br_hmac_context ctx;
br_hmac_key_init(&key_ctx, &br_sha256_vtable, key, sizeof(key));
br_hmac_init(&ctx, &key_ctx, 0);
size_t length_signature = br_hmac_size(&ctx);
br_hmac_update(&ctx, buffer.c_str(), buffer.length());
std::unique_ptr signature(new unsigned char[length_signature]);
size_t length_signature2 = br_hmac_out(&ctx, signature.get());
采用OpenSSL和bearssl结果完全一致,执行结果如下所示:
将上面的Header、Payload、secret值填入jwt.io,得到的结果与程序实现结果一致,如下图所示:
以上代码段的完整code见:GitHub/OpenSSL_Test
GitHub:https://github.com/fengbingchun/OpenSSL_Test