1.Token的用途
在很多計算機系統裡面都少不了使用者認證這一步驟,最常見的認證就是賬號密碼認證,也就是註冊、登入這一流程。
在現實生活中,人也需要認證,大家應該都有個 身份證,回想一下這個身份證是從哪裡來的呢?辦過身份證的應該都知道,一般情況下,身份證需要本人帶著 戶口本 去 公安局 (不知道現在改了木有?)辦理,工作人員在核對了相關資訊,確認無誤的情況下會給你頒發一個身份證, 有效期 一般是10-20年,在一些需要認證的時候,你就可以拿出身份證 校驗 核對身份,比如買火車票,出國,或者辦理其它證件.
很多Web系統裡面token就類似於身份證,賬號密碼就相當於我們的戶口本和本人,需要核對賬號密碼後獲取,拿到token之後就可以使用一些需要認證的服務,而且token也有有效期,和身份證一樣,理論上token必須是唯一。
2.常見的Web認證方式
1.HTTP Basic Auth
這種方式在早期一些Web系統比較常見,就是那種在瀏覽器彈出一個框讓你輸賬號密碼那種,簡單易用,但是缺點一個不安全,其賬號密碼其實是明文(base64encode)傳輸的,而且每次都得帶上。另外就是太醜了。。。
2.Cookies\Session
這種認證方式其實就是類似我們最開始說的身份證這種,只需要輸入一次賬號密碼,認證成功後,系統會將使用者資訊存入session,session是伺服器的本地儲存功能,然後系統根據session生成一個唯一的 sessionid 以cookies的形式傳送給瀏覽器。
cookies是瀏覽器本地儲存,在這套機制裡面的作用是用來儲存sessionid,你也可以不使用cookies儲存,早期有些網站在一些不支援cookies的瀏覽器上面會把sessionid追加到url上面。
cookies裡面儲存的sessionid其實就是相當於身份證編號,每次訪問網站裡面我們帶著這個編號,伺服器拿著編號就可以找到對應的session裡面儲存的資訊,一般情況下里面會儲存一些使用者資訊,比如uid。
講道理這套機制其實問題並不大,大部分時候都管用,但是cookies有一個毛病就是無法跨域,很多大公司有很多網站,這些網站域名可能還不一樣。而且cookies對現在的手機APP支援不好,原生並不支援cookies。最後,就是伺服器儲存session也需要一些開銷,特別是使用者特別多的情況下。還有其它缺點這裡就不列出來了,很多文章都有寫到。
但是其實我想說這套機制大部分情況下是夠用的,特別是對於一些中小型網站來說,簡單易用,快速開發。
3.JWT
一般說到JWT都會提到token,在我的理解裡面token其實就是一個字串,它可以是jwt token,也可以是sessionid token,token就是是一個攜帶認證資訊的字串。
網上關於介紹JWT的文章特別多,大同小異,我們這裡也懶的再說一遍了,貼一個大神的教程,我覺得講的挺清晰了,JSON Web Token 入門教程。
簡單的說,JWT本質上是一種解決方案標準,該方案下一個token應該有3部分組成: Header、Payload、Signature, 其中前2部分差不多就是明文的,都是json 物件,裡面存了一些資訊,使用 base64urlencode 編碼成一個字串。最後的 Signature 是前面2個元素和secret一起加密之後的結果,加密演算法預設是 SHA256, 這個secret應該只有伺服器知道,解密的時候需要用到。
最後生成的token是一個比較長的字串,當使用者登入成功之後可以把這個串返回給瀏覽器,瀏覽器下次請求的時候帶著這個串就行了,問題來了,怎麼帶?很多文章說放到cookies裡面,講道理放到cookies裡面那和sessionid有啥區別? 標準做法是放到HTTP請求的頭資訊Authorization欄位裡面。
伺服器拿到這個串,首先把前面2段的Header和Payload使用 base64urldecode 解碼出來,然後使用剛才使用的加密演算法和secret校驗一下是否和第3段的signature一樣,如果不一樣,則說明這個Token是偽造的,如果一樣,就可以相信Payload裡面的資訊了,一般Payload裡面會存放一些使用者資訊,比如uid,如果Payload裡面需要存放一些敏感資訊,比如手機號,建議先加密Payload。
PHP實戰
下面我將使用PHP構建一個簡單的例子:
JWT類:
<
?phpnamespace App;
class Jwt{
private $alg = 'sha256';
private $secret = "123456";
/** * alg屬性表示簽名的演算法(algorithm),預設是 HMAC SHA256(寫成 HS256);typ屬性表示這個令牌(token)的型別(type),JWT 令牌統一寫為JWT */ public function getHeader() {
$header = [ 'alg' =>
$this->
alg, 'typ' =>
'JWT' ];
return $this->
base64urlEncode(json_encode($header, JSON_UNESCAPED_UNICODE));
} /** * Payload 部分也是一個 JSON 物件,用來存放實際需要傳遞的資料。JWT 規定了7個官方欄位,供選用,這裡可以存放私有資訊,比如uid * @param $uid int 使用者id * @return mixed */ public function getPayload($uid) {
$payload = [ 'iss' =>
'admin', //簽發人 'exp' =>
time() + 600, //過期時間 'sub' =>
'test', //主題 'aud' =>
'every', //受眾 'nbf' =>
time(), //生效時間 'iat' =>
time(), //簽發時間 'jti' =>
10001, //編號 'uid' =>
$uid, //私有資訊,uid ];
return $this->
base64urlEncode(json_encode($payload, JSON_UNESCAPED_UNICODE));
} /** * 生成token,假設現在payload裡面只存一個uid * @param $uid int * @return string */ public function genToken($uid) {
$header = $this->
getHeader();
$payload = $this->
getPayload($uid);
$raw = $header . '.' . $payload;
$token = $raw . '.' . hash_hmac($this->
alg, $raw, $this->
secret);
return $token;
} /** * 解密校驗token,成功的話返回uid * @param $token * @return mixed */ public function verifyToken($token) {
if (!$token) {
return false;
} $tokenArr = explode('.', $token);
if (count($tokenArr) != 3) {
return false;
} $header = $tokenArr[0];
$payload = $tokenArr[1];
$signature = $tokenArr[2];
$payloadArr = json_decode($this->
base64urlDecode($payload), true);
if (!$payloadArr) {
return false;
} //已過期 if (isset($payloadArr['exp']) &
&
$payloadArr['exp'] <
time()) {
return false;
} $expected = hash_hmac($this->
alg, $header . '.' . $payload, $this->
secret);
//簽名不對 if ($expected !== $signature) {
return false;
} return $payloadArr['uid'];
} /** * 安全的base64 url編碼 * @param $data * @return string */ private function base64urlEncode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
} /** * 安全的base64 url解碼 * @param $data * @return bool|string */ private function base64urlDecode($data) {
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}
}複製程式碼
測試:
<
?php$jwt = new \App\Jwt();
//獲取token$token = $jwt->
genToken(1);
//解密token$uid = $jwt->
verifyToken($token);
var_dump($uid);
複製程式碼
以上程式碼僅供參考,實際應用的話最好找個現成的庫,不推薦重複造輪子,jwt的思想是通用的,不分語言,github上面有很多。。。這裡貼一個PHP的庫: firebase/php-jwt。
最後再說說session和jwt的選擇問題,網上隨便搜搜就可以看到很多文章比較這2者優劣,總結就是各有利弊,實際上很多公司既不是session,也不是jwt,可能就是自己搞的類似jwt token這樣的一個字串,然後放在cookies裡面,只要這個串能夠代表一個使用者都可以。