JWT(JSON Web Token)簡介及實現

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 <token>

注:以上內容主要來自網絡整理,主要參考:

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<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 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<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());

	// 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<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 signature
	int 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是調用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<unsigned char[]> 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

GitHubhttps://github.com/fengbingchun/OpenSSL_Test

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章