JWT(Json WEB Token)


1. JWT 簡介

JWTJson WEB Token)是一種采用 Json 方式安裝傳輸信息的方式。 JWT 有針對各種開發語言的庫。 python 中使用的是 PyJWT,它是 PythonJWT 的實現。

https://pypi.python.org/pypi/PyJWT/1.5.3
文檔 https://pyjwt.readthedocs.io/en/latest/

2. JWT 實現機制

服務端往往需要一個 ID 來表示客戶端的身份,那么不使用 session 也可以創建一個 ID 返回給客戶端。但是,要保證客戶端不可篡改。實現機制:

  • 服務端生成一個標識,並使用某種算法對標識簽名,並發送給客戶端。
  • 服務端收到客戶端發來的標識,需要檢查簽名。

這種技術稱作 JWTJson WEB Token)。
這種方案的缺點是,加密、解密需要消耗 CPU 計算資源,無法讓瀏覽器自己主動檢查過期的數據以清除。

3. JWT 應用場景

3.1. 認證

常應用於服務端認證接口的 無 session 方案 ,這是 Jwt 最常用的場景,一旦用戶登錄成功,就會得到 Jwt ,然后請求中就可以帶上這個 Jwt 。服務器中 Jwt 驗證通過,就可以被允許訪問資源。甚至可以在不同域名中傳遞,在單點登錄(Single Sign On)中應用廣泛。

3.2. 數據交換

Jwt 可以防止數據被篡改,它還可以使用公鑰、私鑰,確保請求的發送者是可信的

4. JWT 安裝

pip install pyjwt

5. JWT 原理

5.1. 數據體結構

jwt 生成的 token 分為三部分

  1. header: 由數據類型、加密算法構成
  2. payload: 負載就是要傳輸的數據,一般來說放入python對象即可,會被 json 序列化
  3. signature: 簽名部分。是前面2部分數據分別 base64 編碼后 使用點號連接后,加密算法使用 key 計算好一個結果,再被 base64 編碼,得到簽名

5.2. 提示

所有數據都是明文傳輸的,只是做了base64,如果是敏感信息,請不要使用 jwt
數據簽名的目的不是為了隱藏數據,而是保證數據不被篡改。如果數據篡改了,發回到服務器端,服務器使用自己的 key 再計算一遍,然后進行簽名比對,一定對不上簽名。

5.3. Base64URL

  • HeaderPayload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本類似,但有一些小的不同。
  • JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符 +/=,在 URL 里面有特殊含義,所以要被替換掉:= 被省略、+ 替換成 -/ 替換成 _ 。這就是 Base64URL 算法。

6. JWT 使用

6.1. JWT 的使用方式

客戶端收到服務器返回的 JWT,可以儲存在 Cookie 里面,也可以儲存在 localStorage

此后,客戶端每次與服務器通信,都要帶上這個 JWT

  • 可以把 JWT 放在 Cookie 里面自動發送,但是這樣不能跨域。
  • 更好的做法是把 JWT 放在 HTTP 請求的頭信息 Authorization 字段里面。
    Authorization: Bearer <token>
    
  • 另一種做法是,跨域的時候,JWT 就放在 POST 請求的數據體里面。

6.2. JWT 方法說明

方法 說明
jwt.encode(payload, key, algorithm='HS256', headers=None, json_encoder=None) 生成 jwt, bytes 類型的 base64 編碼
**jwt.decode(jwt, key='', verify=True, algorithms=None, options=None, kwargs) 解碼 jwt 為 bytes 類型的 json
jwt.get_unverified_header(jwt) 返回一個未驗證的 jwt 頭部信息

jwt.encode 參數說明

    def encode(self,
               payload,  # type: Union[Dict, bytes]
               key,  # type: str
               algorithm='HS256',  # type: str
               headers=None,  # type: Optional[Dict]
               json_encoder=None  # type: Optional[Callable]
               ):

jwt.decode 參數說明

    def decode(self,
               jwt,  # type: str
               key='',   # type: str
               verify=True,  # type: bool
               algorithms=None,  # type: List[str]
               options=None,  # type: Dict
               **kwargs):

jwt.get_unverified_header() 參數說明

    def get_unverified_header(self, jwt):
        """Returns back the JWT header parameters as a dict()

        Note: The signature is not verified so the header parameters
        should not be fully trusted until signature verification is complete
        """
        headers = self._load(jwt)[2]
        self._validate_headers(headers)

        return headers

6.3. jwt.encode() 方法使用

import jwt
password = '123456'
# 生成bytes類型的token
token = jwt.encode({"payload":'this is my payload'}, password, 'HS256')

print('    token:',token)
head, payload, signature = token.split(b'.')  # 將成成的token(是bytes類型)切割成三段: head, payload, signature
print('     head:',head)
print('  payload:',payload)
print('signature:',signature)
------------------------------結果--------------------------------------
    token: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXlsb2FkIjoidGhpcyBpcyBteSBwYXlsb2FkIn0.bPGtksZQ-ncY5mya0bF7EmpO82nlN1ohk9L2Dl2D4RA'
     head: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'
  payload: b'eyJwYXlsb2FkIjoidGhpcyBpcyBteSBwYXlsb2FkIn0'
signature: b'bPGtksZQ-ncY5mya0bF7EmpO82nlN1ohk9L2Dl2D4RA'

6.4. jwt.decode() 方法使用

import jwt
import base64
password = '123456'
# 生成bytes類型的token
token = jwt.encode({"payload":'this is my payload'}, password, 'HS256')

# 此函數是為了避免轉碼時候 base64 編碼結尾處缺少 b'=',而報錯.
def addpad(x):
    suffix = 4 - len(x) % 4
    return x + b'=' * suffix

head, payload, signature = token.split(b'.')  # 將成成的token(是bytes類型)切割成三段: head, payload, signature

# 將 token (bytes 類型)轉換為原來的類型
token_decode = jwt.decode(token, password, algorithms=['HS256'])
head_decode = base64.urlsafe_b64decode(addpad(head))
payload_decode = base64.urlsafe_b64decode(addpad(payload))
signature_decode = base64.urlsafe_b64decode(addpad(signature))

print('    token_decode:',token_decode, type(token_decode))
print('     head_decode:',head_decode, type(head_decode))
print('  payload_decode:',payload_decode, type(payload_decode))
print('signature_decode:',signature_decode, type(signature_decode))

------------------------------結果--------------------------------------
    token_decode: {'payload': 'this is my payload'} <class 'dict'>
     head_decode: b'{"typ":"JWT","alg":"HS256"}' <class 'bytes'>
  payload_decode: b'{"payload":"this is my payload"}' <class 'bytes'>
signature_decode: b'l\xf1\xad\x92\xc6P\xfaw\x18\xe6l\x9a\xd1\xb1{\x12jN\xf3i\xe57Z!\x93\xd2\xf6\x0e]\x83\xe1\x10' <class 'bytes'>

6.5. jwt.decode() 方法實現過期驗證

import jwt
import datetime
import threading

event = threading.Event()

passwd = '123456'

token = jwt.encode({
    'a':'test',
    'exp': int(datetime.datetime.now().timestamp() + 5)
    }, passwd, 'HS256')

print(token)
print('===============================')
print('start at about:     ',int(datetime.datetime.now().timestamp()))
while True:
    if not event.wait(1):
        try:
            print(jwt.decode(token, passwd))
        except Exception as e:
            print(e)
            break

執行結果

b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjoidGVzdCIsImV4cCI6MTU1MzQ0MDc5OH0.jzwOxmsp3zqVTXi38hsmu8K7c47j4NV8nPxQe9cQ3E0'
===============================
start at about:      1553440793
{'a': 'test', 'exp': 1553440798}
{'a': 'test', 'exp': 1553440798}
{'a': 'test', 'exp': 1553440798}
{'a': 'test', 'exp': 1553440798}
{'a': 'test', 'exp': 1553440798}
Signature has expired

6.6. jwt.get_unverified_header(jwt) 方法使用

import jwt
password = '123456'
token = jwt.encode({"payload":'this is my payload'}, password, 'HS256')

print('     token:',token)
print('jwt_header:',jwt.get_unverified_header(token))
------------------------------結果--------------------------------------
     token: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXlsb2FkIjoidGhpcyBpcyBteSBwYXlsb2FkIn0.bPGtksZQ-ncY5mya0bF7EmpO82nlN1ohk9L2Dl2D4RA'
jwt_header: {'typ': 'JWT', 'alg': 'HS256'}

7. JWT 的幾個特點

  1. JWT 默認是不加密,但也是可以加密的。生成原始 Token 以后,可以用密鑰再加密一次。
  2. JWT 不加密的情況下,不能將秘密數據寫入 JWT
  3. JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。
  4. JWT 的最大缺點是,由於服務器不保存 session 狀態,因此無法在使用過程中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期之前就會始終有效,除非服務器部署額外的邏輯。
  5. JWT 本身包含了認證信息,一旦泄露,任何人都可以獲得該令牌的所有權限。為了減少盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。
  6. 為了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM