什么是JWT ?
Json web token (JWT), 是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標準((RFC 7519).定義了一種簡潔的,自包含的方法用于通信雙方之間以JSON對象的形式安全的傳遞信息。因為數(shù)字簽名的存在,這些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘鑰對進行簽名。
JWT請求流程

1.用戶使用賬號和面發(fā)出post請求;
2.服務(wù)器使用私鑰創(chuàng)建一個jwt;
3.服務(wù)器返回這個jwt給瀏覽器;
4.瀏覽器將該jwt串在請求頭中像服務(wù)器發(fā)送請求;
5.服務(wù)器驗證該jwt;
6.返回響應(yīng)的資源給瀏覽器。
JWT的主要應(yīng)用場景
身份認證在這種場景下,一旦用戶完成了登陸,在接下來的每個請求中包含JWT,可以用來驗證用戶身份以及對路由,服務(wù)和資源的訪問權(quán)限進行驗證。由于它的開銷非常小,可以輕松的在不同域名的系統(tǒng)中傳遞,所有目前在單點登錄(SSO)中比較廣泛的使用了該技術(shù)。
信息交換在通信的雙方之間使用JWT對數(shù)據(jù)進行編碼是一種非常安全的方式,由于它的信息是經(jīng)過簽名的,可以確保發(fā)送者發(fā)送的信息是沒有經(jīng)過偽造的。
優(yōu)點:
1.簡潔(Compact): 可以通過URL,POST參數(shù)或者在HTTP header發(fā)送,因為數(shù)據(jù)量小,傳輸速度也很快
2.自包含(Self-contained):負載中包含了所有用戶所需要的信息,避免了多次查詢數(shù)據(jù)庫
3.因為Token是以JSON加密的形式保存在客戶端的,所以JWT是跨語言的,原則上任何web形式都支持。
不需要在服務(wù)端保存會話信息。
JWT的結(jié)構(gòu)
JWT是由三段信息構(gòu)成的,將這三段信息文本用.連接一起就構(gòu)成了JWT字符串。
如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT包含了三部分:
1.Header 頭部(標題包含了令牌的元數(shù)據(jù),并且包含簽名和/或加密算法的類型)
2.Payload 負載 (類似于飛機上承載的物品)
3.Signature 簽名/簽證
Header
JWT的頭部承載兩部分信息:token類型和采用的加密算法。
{
"alg": "HS256",
"typ": "JWT"
}
聲明類型:這里是jwt
聲明加密的算法:通常直接使用 HMAC SHA256
加密算法是單向函數(shù)散列算法,常見的有MD5、SHA、HAMC。
MD5(message-digest algorithm 5) (信息-摘要算法)縮寫,廣泛用于加密和解密技術(shù),常用于文件校驗。校驗?不管文件多大,經(jīng)過MD5后都能生成唯一的MD5值
SHA (Secure Hash Algorithm,安全散列算法),數(shù)字簽名等密碼學(xué)應(yīng)用中重要的工具,安全性高于MD5
HMAC (Hash Message Authentication Code),散列消息鑒別碼,基于密鑰的Hash算法的認證協(xié)議。用公開函數(shù)和密鑰產(chǎn)生一個固定長度的值作為認證標識,用這個標識鑒別消息的完整性。常用于接口簽名驗證.
Payload
載荷就是存放有效信息的地方。
有效信息包含三個部分
1.標準中注冊的聲明
2.公共的聲明
3.私有的聲明
標準中注冊的聲明 (建議但不強制使用) :
iss: jwt簽發(fā)者
sub: 面向的用戶(jwt所面向的用戶)
aud: 接收jwt的一方
exp: 過期時間戳(jwt的過期時間,這個過期時間必須要大于簽發(fā)時間)
nbf: 定義在什么時間之前,該jwt都是不可用的.
iat: jwt的簽發(fā)時間
jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
公共的聲明:
公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息.但不建議添加敏感信息,因為該部分在客戶端可解密.
私有的聲明:
私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味著該部分信息可以歸類為明文信息。
Signature
jwt的第三部分是一個簽證信息
這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret組合加密,然后就構(gòu)成了jwt的第三部分。
密鑰secret是保存在服務(wù)端的,服務(wù)端會根據(jù)這個密鑰進行生成token和進行驗證,所以需要保護好。
代碼如下:
class Jwt {
//頭部
private static $header=array(
'alg'=>'HS256', //生成signature的算法
'typ'=>'JWT' //類型
);
//使用HMAC生成信息摘要時所使用的密鑰
private static $key='123456';
/**
* 獲取jwt token
* @param array $payload jwt載荷 格式如下非必須
* [
* 'iss'=>'jwt_admin', //該JWT的簽發(fā)者
* 'iat'=>time(), //簽發(fā)時間
* 'exp'=>time()+7200, //過期時間
* 'nbf'=>time()+60, //該時間之前不接收處理該Token
* 'sub'=>'www.admin.com', //面向的用戶
* 'jti'=>md5(uniqid('JWT').time()) //該Token唯一標識
* ]
* @return bool|string
*/
public static function getToken(array $payload)
{
if(is_array($payload))
{
$base64header=self::base64UrlEncode(json_encode(self::$header,JSON_UNESCAPED_UNICODE));
$base64payload=self::base64UrlEncode(json_encode($payload,JSON_UNESCAPED_UNICODE));
$token=$base64header.'.'.$base64payload.'.'.self::signature($base64header.'.'.$base64payload,self::$key,self::$header['alg']);
return $token;
}else{
return false;
}
}
/**
* 驗證token是否有效,默認驗證exp,nbf,iat時間
* @param string $Token 需要驗證的token
* @return bool|string
*/
public static function verifyToken($Token)
{
$tokens = explode('.', $Token);
if (count($tokens) != 3)
return false;
list($base64header, $base64payload, $sign) = $tokens;
//獲取jwt算法
$base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
if (empty($base64decodeheader['alg']))
return false;
//簽名驗證
if (self::signature($base64header . '.' . $base64payload, self::$key, $base64decodeheader['alg']) !== $sign)
return false;
$payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);
//簽發(fā)時間大于當(dāng)前服務(wù)器時間驗證失敗
if (isset($payload['iat']) && $payload['iat'] > time())
return false;
//過期時間小宇當(dāng)前服務(wù)器時間驗證失敗
if (isset($payload['exp']) && $payload['exp'] < time())
return false;
//該nbf時間之前不接收處理該Token
if (isset($payload['nbf']) && $payload['nbf'] > time())
return false;
return $payload;
}
/**
* base64UrlEncode https://jwt.io/ 中base64UrlEncode編碼實現(xiàn)
* @param string $input 需要編碼的字符串
* @return string
*/
private static function base64UrlEncode($input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
/**
* base64UrlEncode https://jwt.io/ 中base64UrlEncode解碼實現(xiàn)
* @param string $input 需要解碼的字符串
* @return bool|string
*/
private static function base64UrlDecode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$addlen = 4 - $remainder;
$input .= str_repeat('=', $addlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
/**
* HMACSHA256簽名 https://jwt.io/ 中HMACSHA256簽名實現(xiàn)
* @param string $input 為base64UrlEncode(header).".".base64UrlEncode(payload)
* @param string $key
* @param string $alg 算法方式
* @return mixed
*/
private static function signature($input, $key, $alg = 'HS256')
{
$alg_config=array(
'HS256'=>'sha256'
);
return self::base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key,true));
}
}
使用示示例
$payload_test=array('iss'=>'admin','iat'=>time(),'exp'=>time()+7200,'nbf'=>time(),'sub'=>'www.admin.com','jti'=>md5(uniqid('JWT').time()));;
$token_test=Jwt::getToken($payload_test);
echo "<pre>";
echo $token_test;
$getPayload_test=Jwt::verifyToken($token_test);
echo "<br><br>";
var_dump($getPayload_test);
echo "<br><br>";
token 存放
通常應(yīng)該在請求的header頭中的 Authorization字段使用 Bearer模式添加JWT(Authorization: Bearer )
服務(wù)器端 $_SERVER[HTTP_AUTHORIZATION]: 獲取
使用方式 根據(jù)自己的應(yīng)用場景自行調(diào)節(jié)