JWT(JSON Web Token)简介及实现
JWT(JSON Web Token):是一個(gè)開放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊且自包含的方式,用于在各方之間作為Json對(duì)象安全地傳輸信息。由于此信息是經(jīng)過(guò)數(shù)字簽名的,因此可以被驗(yàn)證和信任。可以使用HMAC SHA256或RSA等對(duì)JWT進(jìn)行簽名。
JWT的組成:它是一個(gè)很長(zhǎng)的字符串,中間用點(diǎn)(.)分隔成三個(gè)部分。它的三個(gè)部分依次是:Header(頭部)、Payload(載荷)、Signature(簽名)。JWT默認(rèn)是不加密的。
Header:是一個(gè)Json對(duì)象,描述JWT的元數(shù)據(jù),例子如下:alg屬性表示簽名的算法,默認(rèn)是HMAC SHA256,寫成HS256,也可使用RSA;typ屬性表示這個(gè)令牌(token)的類型,JWT令牌統(tǒng)一寫為JWT;id屬性是用戶自定義的。最后將此Json對(duì)象使用base64url編碼成字符串。
{"alg": "HS256","typ": "JWT","id": "fengbingchun"
}
Payload:也是一個(gè)Json對(duì)象,用來(lái)存放實(shí)際需要傳遞的數(shù)據(jù)。JWT規(guī)定了7個(gè)官方字段,供選用:iss(Issuer):簽發(fā)人;exp(Expiration Time):過(guò)期時(shí)間;sub(Subject):主題;aud(Audience):受眾;nbf(Not Before):生效時(shí)間;iat(Issued At):簽發(fā)時(shí)間;jti(JWT ID):編號(hào)。除了官方字段,你還可以在這個(gè)部分定義私有字段。最后此Json對(duì)象也要使用base64url編碼成字符串。例子如下:
{"csdn": "https://blog.csdn.net/fengbingchun","github": "https://github.com//fengbingchun"
}
Signature:是對(duì)前兩部分的簽名,防止數(shù)據(jù)篡改。首先需要指定一個(gè)密鑰(secret),然后使用Header里面指定的簽名算法(默認(rèn)是HMAC SHA256),按照以下的方式產(chǎn)生簽名:算出簽名后,也需要把此簽名通過(guò)base64url編碼成字符串。最后把Header、Payload、Signature三個(gè)部分編碼成的字符串拼成一個(gè)字符串,每個(gè)部分之間用”點(diǎn)”(.)分隔,形式如xxxx.yyyy.zzzz。
HMACSHA256(base64urlEncode(header) + "." + base64urlEncode(payload), secret)
base64url和base64區(qū)別:base64有三個(gè)字符+、/和=,在URL里面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_,這就是base64url。
一般此JWT會(huì)放在HTTP請(qǐng)求的頭信息Authorization字段里:
Authorization: Bearer <token>
注:以上內(nèi)容主要來(lái)自網(wǎng)絡(luò)整理,主要參考:
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
以下是代碼實(shí)現(xiàn):
Header和Payload內(nèi)容如上所示,secret值為:"1234567890+-!@#$%^&*x()_=QF{></?",代碼段如下:
int test_jwt()
{// encode headerconst 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<char[]> 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 payloadconst 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<char[]> 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());// signaturestd::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<unsigned char[]> 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<const unsigned char*>(buffer.c_str()), buffer.length());HMAC_Final(ctx, signature.get(), &length_signature);HMAC_CTX_free(ctx);// encode signatureint length_encoded_signature = (length_signature + 2) / 3 * 4;std::unique_ptr<char[]> 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是調(diào)用OpenSSL的接口實(shí)現(xiàn)的,也可調(diào)用bearssl接口實(shí)現(xiàn),代碼段如下:
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<unsigned char[]> signature(new unsigned char[length_signature]);size_t length_signature2 = br_hmac_out(&ctx, signature.get());
采用OpenSSL和bearssl結(jié)果完全一致,執(zhí)行結(jié)果如下所示:
將上面的Header、Payload、secret值填入jwt.io,得到的結(jié)果與程序?qū)崿F(xiàn)結(jié)果一致,如下圖所示:
以上代碼段的完整code見:GitHub/OpenSSL_Test
GitHub:https://github.com/fengbingchun/OpenSSL_Test
總結(jié)
以上是生活随笔為你收集整理的JWT(JSON Web Token)简介及实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C和C++安全编码笔记:格式化输出
- 下一篇: C和C++安全编码笔记:并发