1. 程式人生 > 實用技巧 >毒瘤數學題彙總(二)

毒瘤數學題彙總(二)

什麼是 jwt

jwt 全稱 json web token,是一種基於JSON的、用於在網路上安全的表示雙方之間的宣告。目前,jwt廣泛應用在系統的使用者認證方面,特別是現在前後端分離專案。

由於http 協議是一種無狀態的協議,無法對客戶端的身份進行認證和唯一標識,所以需要採用其他的手段來實現。

傳統的session方案和jwt方式實現

傳統的cookie-sessio機制也是為了解決客戶端的身份驗證而出現的,伺服器同通過儲存客戶端的資料,併發送給客戶端一個隨機字串作為session_id,這樣每次請求時伺服器可以根據每個客戶端的session_id來找到儲存在伺服器的資料,從而實現客戶端的身份認證,但是這種方式在現在的使用種存在問題,例如session是儲存在伺服器的記憶體中(保證效率)的,這樣會耗費大量的伺服器記憶體資源,並且在目前的分散式的服務中,session存在與單獨一臺伺服器中,而這個session無法與其他主機共享,已經登入的客戶端請求轉到其他伺服器上將會丟失客戶端狀態。

上述問題的存在總的來說還是session儲存到服務端,並且沒有進行共享而導致的,解決的方案是使用單獨的session服務,保證session資訊共享,這也是常用的做法,例如使用一個redis叢集作為session儲存的解決方案。

另一種解決方式則是使 jwt 的方案,它不同於將客戶端的資訊儲存到伺服器,而是通過一個jwt字串將這些資料傳送到對應的客戶端,讓每個客戶端各自儲存自己的資料,下一次請求伺服器時,帶上這個jwt字串,從中獲取這個字串中獲取資料即可,並且為了解決資料儲存到客戶端而存在被篡改的問題,jwt 使用一個演算法將 資料+鹽 生成一個簽名新增到字串中,這個簽名只有知道鹽的伺服器了可以生成能被自己驗證通過的簽名。所以客戶端想要篡改資料,就必須能夠根據資料生成這個簽名,也就需要伺服器的這個鹽。只要鹽是安全的,他人就只能通過其他方式破解,這種概率是比較低的。

jwt的組成

jwt是一個固定格式的,以點分成三段的字串,形式如下。

aDJIGghOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpSDGfvaG4gRG9lSGEIiwiaDSGWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

三個部分有各自的作用,前兩部分都是可以通過BASE64反解得到明文資料的,而第三部分則是更具前兩部分生成的簽名。分別介紹以下三部分的內容以及生成過程。

第一部分

第一段被稱為HEADER部分,包含固定的演算法和token型別資料,然後將該資料的json字串進行base64url編碼得到。該頭資訊說明了這是一個JWT型別的token,並且在生成簽名時候,使用的是HS256演算法。

{
  "alg": "HS256",
  "typ": "JWT"
}

第二部分

第二部分被稱為payload部分,這個部分可以包含使用者的個人資訊內容,用於儲存使用者的狀態資訊等,然後同樣的通過base64url編碼,例如可以記錄使用者的id,姓名等,但是不能儲存敏感的資訊,例如密碼等,因為這部分資料可以被反解,所以等同於明文儲存,如果該字串被其他惡意使用者獲得,將會造成嚴重的安全問題。

{
  "UserId": "1001",
  "Name": "tom",
  "iat": 1516239022
  ...
}

資料使用json的格式儲存即可,示例中UserId和Name都是自定義的資料,而iat是JWT的保留字,該key代表了特殊的含義,即該字串簽發時的時間戳資訊,同時還可以指定過期時間等資訊,這些保留的關鍵字包括:

iss(Issuser):代表這個JWT的簽發主體;
sub(Subject):代表這個JWT的主體,即它的所有人;
aud(Audience):代表這個JWT的接收物件;
exp(Expiration time):是一個時間戳,代表這個JWT的過期時間;
nbf(Not Before):是一個時間戳,代表這個JWT生效的開始時間,意味著在這個時間之前驗證JWT是會失敗的;
iat(Issued at):是一個時間戳,代表這個JWT的簽發時間;
jti(JWT ID):是JWT的唯一標識。

第三部分

第三部分即簽名,簽名由生成該字串的服務端生成。生成的過程是將前兩部分的Base64url編碼後的內容通過點拼接起來,然後對其進行HS256進行加密,再進行base64編碼,得到第三部分的內容。

base64url(
    HMACSHA256(
      base64UrlEncode(header) + "." + base64UrlEncode(payload),
      your-256-bit-secret (祕鑰加鹽)
    )
)

最後將三段字串通過.拼接起來就生成了jwt的token。

python實現

瞭解了jwt的生成方式,即可以根據上述的方式來生成一個合法的jwt字串,而這個功能已經有現成的模組實現,即pyjwt模組,使用該模組即可簡單生成jwt字串和驗證jwt,如果需要了解具體過程,檢視pyjwt原始碼即可。

安裝模組

pip3 install pyjwt

生成jwt

import jwt
import datetime
from jwt import exceptions
SALT = 'iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv='
def create_token():
    # 第一部分內容,構造header
    headers = {
        'typ': 'jwt',
        'alg': 'HS256'
    }
    # 第二部分內容 構造payload
    payload = {
        'user_id': 1, # 自定義使用者ID
        'username': 'wupeiqi', # 自定義使用者名稱
        'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5) # 超時時間
    }

   # 第三部分,使用前兩部分內容,加鹽(SALT),並進行BASEurl64編碼得到jwt result
= jwt.encode(payload=payload, key=SALT, algorithm="HS256", headers=headers).decode('utf-8') return result if __name__ == '__main__': token = create_token() print(token)

校驗jwt

伺服器得到使用者的jwt後,需要判斷該資料的合法性,通過驗證簽名,確保資料沒有被篡改。同時驗證是否過期,驗證成功後,從第二部分中提取內容即可。

詳細過程:

  • 將token分割成headerpayloadcrypto三部分。
  • 對第一部分header進行base64url解密,得到header

  • 對第二部分payload進行base64url解密,得到payload

  • 對第三部分crypto進行base64url解密,得到signature

  • 驗證第三部分signature部分資料

    • 使用 . 拼接前兩段密文。
    • 從第一段明文中獲取加密演算法,預設:HS256
    • 使用 加密演算法 對 鹽 + header +payload進行加密,將得到的結果和signature密文進行比較。如果相同,則校驗通過
import jwt
import datetime
from jwt import exceptions
def get_payload(token):
    """
    根據token獲取payload
    :param token:
    :return:
    """
    try:
        # 從token中獲取payload【不校驗合法性】
        # unverified_payload = jwt.decode(token, None, False)
        # print(unverified_payload)
        # 從token中獲取payload【校驗合法性】
        verified_payload = jwt.decode(token, SALT, True)
        return verified_payload
    except exceptions.ExpiredSignatureError:
        print('token已失效')
    except jwt.DecodeError:
        print('token認證失敗')
    except jwt.InvalidTokenError:
        print('非法的token')
if __name__ == '__main__':
    token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU"
    payload = get_payload(token)

通過jwt模組的decode方法將會自動對jwt的過期時間,資料的合法性等等進行檢驗(需要指定特定的引數),如果校驗通過則返回payload資料。否則丟擲異常。得到payload資料後,將這個字串解析為的字典格式就能方便的提取內部的內容了。