實現Token的方式有很多,本篇介紹的是利用Json Web Token(JWT)生成的Token.JWT生成的Token有什麼好處呢?
- 安全性比較高,加上密匙加密而且支持多種算法。
- 攜帶的信息是自定義的,而且可以做到驗證token是否過期。
- 驗證信息可以由前端保存,後端不需要爲保存token消耗內存。
1.JWT構成
第一部分我們稱它爲頭部(header),第二部分我們稱其爲載荷(payload),第三部分是簽證(signature).網上也有很多其他資料關於header 、payload的格式,其中要知道:
header,頭部信息主要包括(參數的類型--JWT,簽名的算法--HS256、HS512等等)
poyload,負荷基本就是自己想要存放的信息(因爲信息會暴露,不應該在載荷裏面加入任何敏感的數據)
sign,簽名的作用就是爲了防止惡意篡改數據.
最終的token : xxxxxx.yyyyyyyy.zzzzzz
2.服務器端進行鑑權
(1)客戶端連接服務端時,攜帶的鑑權token 比如:(以下的所有源碼中需要用到的token信息都是如下token)
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJRTVVMVVhBQSIsInNjb3BlIjpbIlJPTEVfQ0xJRU5U
Il0sImlzcyI6Imh0dHA6Ly9za3lsaWdodC5jb20uaGsiLCJpZCI6ImZmNTdjOWUzLWI4YTItNGVkYi05
MDUzLTc1MzMxZTg4ZjQ4YSIsImV4cCI6MTU1MzA1MTI5NSwiaWF0IjoxNTUzMDQ3Njk1fQ.Em8HJJM2vCK2bqYD5qw-Czxz__hYuOw-DiDHYZPxmzH5clYIAFJ9WUgciihdbps8Fmm88gspYFoHqYRz8X5BfA
我們可以拿到base64後的head:eyJhbGciOiJIUzUxMiJ9
base64後的payload:
eyJzdWIiOiJRTVVMVVhBQSIsInNjb3BlIjpbIlJPTEVfQ0xJRU5UIl0sImlzcyI6Imh0dHA6Ly9za3lsaWdodC5jb20uaGsiLCJpZCI6ImZmNTdjOWUzLWI4YTItNGVkYi05MDUzLTc1MzMxZTg4ZjQ4YSIsImV4cCI6MTU1MzA1MTI5NSwiaWF0IjoxNTUzMDQ3Njk1fQ
以及sign:
Em8HJJM2vCK2bqYD5qw-Czxz__hYuOw-DiDHYZPxmzH5clYIAFJ9WUgciihdbps8Fmm88gspYFoHqYRz8X5BfA
鑑權是如何通過的呢?或者說,我們認爲服務端如何判斷客戶端鑑權通過與否呢?
在生成token時:
jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:
- header (base64後的)
- payload (base64後的)
- secret
這個部分需要base64加密後的header和base64加密後的payload使用.
連接組成的字符串,然後通過header中聲明的加密方式進行secret(密鑰)
組合加密,然後就構成了JWT的第三部分。
// javascript 以HMACSHA256算法爲例
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret');
(2)服務端鑑權
可以對header進行base64解碼,得到具體的算法,代碼如下:
Base64解碼函數:
/*
Base64解碼
*/
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static inline bool is_base64(unsigned char c)
{
return (isalnum(c) || (c == '+') || (c == '/'));
}
std::string Base64Decode(std::string const& encoded_string)
{
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
{
char_array_4[i++] = encoded_string[in_]; in_++;
if (i == 4)
{
for (i = 0; i < 4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if (i)
{
for (j = i; j < 4; j++)
char_array_4[j] = 0;
for (j = 0; j < 4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}
當我們將頭(eyJhbGciOiJIUzUxMiJ9)解析出來,信息如下,因此就可以知道是用什麼算法加密;
int main(int argc, char argv[])
{
std::string headerstr = Base64Decode("eyJhbGciOiJIUzUxMiJ9");
printf("header:%s\n", headerstr.c_str());
/*
header:{"alg":"HS512"}
*/
return 0;
}
此時,我們做這樣的操作:
encode = Base64Encode(header) + '.' +Base64Encode(payload),即是token中的xxxx.yyyyy部分,因此對於服務端無需再做Base64操作。
在進行具體的sign過程,先要知道Base64Encode實現過程,接下來會用到:
std::string Base64Encode(unsigned char const* bytes_to_encode, unsigned int in_len)
{
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--)
{
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3)
{
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (i = 0; (i < 4); i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}
if (i)
{
for (j = i; j < 3; j++)
char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];
while ((i++ < 3))
ret += '=';
}
return ret;
}
接下來,使用HMAC512加密,密鑰我們認爲服務端已經獲取,權限驗證過程如下:
#include <string>
#include <stdint.h>
#include "openssl/hmac.h"
#include "openssl/evp.h" //使用openssl
#include<algorithm>
#include<string>
#include<iostream>
int main(int argc, char argv[])
{
//注意密鑰
const char * key = "ee394b990568d08a";
const char str[] = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJRTVVMVVhBQSIsInNjb3BlIjpbIlJPTEVfQ0xJRU5UIl0sImlzcyI6Imh0dHA6Ly9za3lsaWdodC5jb20uaGsiLCJpZCI6ImZmNTdjOWUzLWI4YTItNGVkYi05MDUzLTc1MzMxZTg4ZjQ4YSIsImV4cCI6MTU1MzA1MTI5NSwiaWF0IjoxNTUzMDQ3Njk1fQ";
HMAC_CTX mdctx;
const EVP_MD * evpmd = NULL;
evpmd = EVP_sha512(); //算法
HMAC_CTX_init(&mdctx);
HMAC_Init_ex(&mdctx, key, strlen(key), evpmd, NULL);
unsigned char * outdate = (unsigned char*)malloc(EVP_MAX_MD_SIZE);
unsigned int size = 0;
HMAC_Update(&mdctx, (const unsigned char *)str, strlen(str));
HMAC_Final(&mdctx, outdate, &size);
HMAC_CTX_cleanup(&mdctx);
for (int i = 0; i < size; i++)
{
printf("%x", (unsigned int)outdate[i]);
}
printf("\n");
//注意:此時得到的outdate並非是token中的sign,還需要繼續處理,如下:
std::string encode = Base64Encode(outdate, size); //使用Base64Encode
printf("encode:%s\n", encode.c_str());
std::string signature = encode;
//思考:爲什麼接下來要對符號處理??這操作不能缺少!!
//////////////////////////////////////////////////////////////////////////////
replace(signature.begin(), signature.end(), '+', '-');
replace(signature.begin(), signature.end(), '/', '_');
std::string::iterator iter;
for (iter = signature.begin(); iter != signature.end(); iter++)
if (*iter == '=')
{
signature.erase(iter);
iter--;
}
///////////////////////////////////////////////////////////////////////////////
printf("signature:%s\n", signature.c_str());
int lsuccess = 0;
lsuccess = strncmp("Em8HJJM2vCK2bqYD5qw-Czxz__hYuOw-DiDHYZPxmzH5clYIAFJ9WUgciihdbps8Fmm88gspYFoHqYRz8X5BfA", signature.c_str(), signature.size());
//字符串比較相等,鑑權也就通過了,簽名OK
printf("lsuccess:%d\n", lsuccess);
return 0;
}
注:以上的Base64Encode,Base64Decode,都是經過驗證,方法可以正常使用,如有需要自己保留,如有問題歡迎留言指導,謝謝![歡迎添加微信公總號:yansuirengui ]