JWT 全稱叫 JSON Web Token, 是一個很是輕巧的規範。這個規範容許咱們使用 JWT 在用戶和服務器之間傳遞安全可靠的信息。php
jwt
用圖普遍,例如受權
、鑑權
等。具體一點的話,假如咱們有一個 A 用戶想要邀請某用戶進入本身的羣組,此時 A 用戶須要生成一條邀請連接,連接內容大體以下: https://host/group/{group_id}/invite/{invite_user}
html
此時這個連接點擊進去雖然能夠實現讓用戶加入羣組,可是用戶能夠隨意更改這個連接的參數,例如改改 group 後面的ID,從而加入其餘任意羣組,改改 invite 後面的邀請人等等操做。因此這種 URL 並非安全的,那麼這種狀況下,咱們就可使用 jwt
來實建立一個安全的邀請連接了。git
首先 URL 要簡單改一下, https://host/group/invite/{token}
能夠看到咱們去掉了 groupId 和 inviteUser 參數,添加了一個 token
參數,可想而知, groupId 和 inviteUser 應該是被包含進 token
裏面了,如何實現這個看似很神奇的 token 呢? 咱們來看看 jwt 的原理吧。github
在講 jwt 原理以前得先知道 jwt 由哪些東西組成。web
一個 JWT 實際上就是一個字符串,它由三部分組成,頭部、載荷與簽名。算法
JWT 須要一個頭部,用於描述關於該 JWT 的最基本的信息,例如其類型以及簽名所用的算法等。這也能夠被表示成一個 JSON 對象,如:json
{ "typ": "JWT", "alg": "md5" }
將上面的 json 字符串使用 base64 進行編碼後,能夠獲得一下內容,咱們稱其爲 JWT 的頭部(Header)。安全
eyJ0eXAiOiJqd3QiLCJhbGciOiJtZDUifQ==
咱們先將上面的邀請入羣的操做描述成一個 JSON 對象。其中添加了一些其餘的信息,幫助從此收到這個 JWT 的服務器理解這個JWT。服務器
{ "sub": "1", "iss": "http://host/group/invite", "iat": 1451888119, "exp": 1454516119, "nbf": 1451888119, "jti": "37c107e4609ddbcc9c096ea5ee76c667", "group_id": 1, "invite_user": "A" }
這裏面的前6個字段都是由JWT的標準所定義的。app
將上面的 json 字符串使用 base64 進行編碼後,能夠獲得一下內容,咱們稱其爲 JWT 的載荷(Payload)。
eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTUyNzY2NzY2MywiaWF0IjoxNTI3NjY0MDYzLCJuYmYiOjE1Mjc2NjQwNjMsImdyb3VwX2lkIjoxLCJpbnZpdGVfdXNlciI6IkEiLCJqdGkiOiJlMjE4ZTJhZDdlYTdmZjUzYTVhM2RlZjA0MmFjMjM4NCJ9
在簽名以前咱們須要先獲得用於簽名的字符串, 將頭部和載荷使用 .
進行拼接(頭部在前), 獲得用於簽名的字符串
eyJ0eXAiOiJqd3QiLCJhbGciOiJtZDUifQ==.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTUyNzY2NzY2MywiaWF0IjoxNTI3NjY0MDYzLCJuYmYiOjE1Mjc2NjQwNjMsImdyb3VwX2lkIjoxLCJpbnZpdGVfdXNlciI6IkEiLCJqdGkiOiJlMjE4ZTJhZDdlYTdmZjUzYTVhM2RlZjA0MmFjMjM4NCJ9
而後使用簽名方法對用於簽名的字符串進行簽名, 獲得以下字符串,即 簽名(Signature)
NDljMzljOTkyOGNmYWU1NGEyZDYzMTk5NTNlNGEwZDA=
最後把用於簽名的字符串和簽名使用 .
進行拼接(簽名在後), 便可獲得 一個完整的 token
。可是,此時的 token
沒有帶上籤發者特有的標誌,是能夠被僞造的,至於如何解決這個問題咱們下面 jwt 具體實現會講。
上面說完 jwt 組成,相信你已經知道 jwt 大概是個啥子東西了 --- 就是一個字符串!!!
那麼這個字符串如何保證不被篡改呢 ? 這裏就要引入 secret
了。
回到上面的例子,邀請用戶入羣這個場景,雖然咱們上面把 參數改爲了 token 這種形式,可是你可能會發現,這樣的 token 別人捕獲了以後,任然能夠本身僞造一個相似的 token ,由於此時的簽名(Signature)
並無簽發者特有的身份信息,全部數據都是明文的,因此這樣簽名是不安全的,應該加上 secret
進行簽名。
簽發者須要準備一個能夠確認本身身份的字符串,這個字符串咱們稱之爲 secret
。以 md5
做爲簽名方法爲例(並不建議使用 md5 做爲簽名方法)
,咱們只須要將上面準備的 用於簽名的字符串簡單的與 secret
進行拼接,而後進行 md5 計算,這時候獲得的簽名是受 secret
值影響的,因此即使他人捕獲了以後 token
,他仍然不能隨意篡改 token 的內容,由於他不知道 secret
和拼接方法,故此時的 token
是安全的,不可被惡意篡改的。
$signatureString = 'pen'; // 原始數據 $secret = 'apple'; // 簽發者 secret $originSignature = md5($signatureString .'-'. $secret); print_r($signature); // apple-pen $signatureString = 'pen'; // 原始數據 $secret = 'pineapple'; // 不同的 secret $fakeSignature = md5($signatureString .'-'. $secret); print_r($signature); // pineapple-pen // 能夠看到不同的 secret 會生成徹底不同的簽名,這樣咱們的數據就能夠保證不能被隨意篡改了~
是的,jwt 的頭部和載荷字段均可以被解碼(base64 屬於編碼,是能夠被解碼的)
。因此並不建議用 jwt 傳輸敏感信息,例如密碼,由於這很容易被捕獲後解碼,從而被竊取。
咱們能夠將 簽發者信息描述成一個 json ,而後對這個 json 字符串進行編碼,這樣一樣能夠獲得一個 secret 字符串。
先來一個最粗暴的 jwt 實現
jwt for php
實現class JWT { protected $headers; protected $payload; /** * @return array */ public function getHeaders(): array { return $this->headers; } /** * @return array */ public function getPayload(): array { return $this->payload; } public function __construct(array $headers, array $payload) { $this->setHeaders($headers); $this->setPayload($payload); } public function setHeaders(array $headers): void { $this->headers = $headers; } public function setPayload(array $payload): void { $this->payload = $payload; } /** * 獲取用於簽名的字符串 * * @return string */ public function signatureStr(): string { $headersStr = $this::encodeStr(json_encode($this->headers)); $payloadStr = $this::encodeStr(json_encode($this->payload)); return "{$headersStr}.{$payloadStr}"; } /** * 編碼 * * @param string $string * * @return string */ protected static function encodeStr(string $string): string { return rtrim(strtr(base64_encode($string), '+/', '-_'), '='); } /** * 解碼 * * @param string $string * * @return string */ protected static function decodeStr(string $string): string { return base64_decode(strtr($string, '-_', '+/')); } /** * 簽名,此時的 secret 爲 qbhy * * @param string $string * * @return string */ protected static function signature(string $string): string { return md5($string . 'qbhy'); } /** * 校驗簽名 * * @param string $signStr * @param string $sign * * @return bool */ protected static function checkSignature(string $signStr, string $sign): bool { return static::signature($signStr) === $sign; } /** * 生成 token * * @return string */ public function token(): string { $signStr = $this->signatureStr(); $token = $signStr . '.' . $this::signature($signStr); return $token; } /** * 從 token 中獲取數據 * * @param string $token * * @return \App\Modules\JWT\JWT * @throws \App\Modules\JWT\JWTException */ public static function fromToken(string $token): JWT { $arr = explode('.', $token); if (count($arr) !== 3) { throw new JWTException('token 錯誤'); } if (!static::checkSignature("{$arr[0]}.{$arr[1]}", $arr[2])) { throw new JWTException('簽名錯誤'); } $headers = json_decode(static::decodeStr($arr[0]), true); $payload = json_decode(static::decodeStr($arr[1]), true); return new static($headers, $payload); } }
這裏先安利一下我寫的一個基於 php 的 jwt 擴展包 --- 96qbhy/simple-jwt
, 這個包實現了完整的 jwt 規範,開箱即用,你能夠基於 96qbhy/simple-jwt
來給你的應用添加 jwt 相關功能。
我把 simple-jwt 拆分紅,Encoder(編碼器)
、Encrypter(簽名器)
、JWT
、JWTManager
四部分,你能夠自行擴展 Encoder
、Encrypter
,從而實現本身的編碼和加密方法,感興趣的同窗能夠去 github
看看源碼 96qbhy/simple-jwt 。有問題歡迎與我討論,同時歡迎 Issue
和 PR
。若有錯誤歡迎指出,謝謝。