JWT介紹
JWT的全稱為Json web token,是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標准((RFC 7519).該token被設計為緊湊且安全的,特別適用於分布式站點的單點登錄(SSO)場景。
JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
基本構成
JWT的本質就是token,它主要有三部分組成,分別是頭部(header)、荷載(payload)主題部、以及簽證(signature)。
前兩部分都是由base64進行編碼(可反接的加密),后一部分是不可反解的加密,由前兩部分base64的結果加密(hash256)后組成。
各部分之間用.進行分割,如下所示:
eyJ0eXBlIjogIkpXVCIsICJhbGciOiAiSEFTSDI1NiJ9.7B226964223A202231303031222C20226E616D65223A202279756E7961222C2022616765223A20223138227D.3495bb47139c954ba17c3081262c55d2b436346e0d58990202cc668b7bca54ac
header
header中一般都具有兩種信息:
聲明類型,這里是jwt
聲明加密的算法 通常直接使用 HMAC SHA256
當然你也可以添加上其他信息,如公司名稱等。這都是允許的
下面進行自定義頭部,JSON格式:
{
"type":"JWT",
"alg":"HASH256"
}
然后使用base64對其進行編碼,得到JWT中的header部分:
eyJ0eXBlIjogIkpXVCIsICJhbGciOiAiSEFTSDI1NiJ9
payload
荷載部作為JWT三部分中的第二部分,都是存放有效信息。它可以存放三種類型的有效信息:
標准中注冊的聲明
公共的聲明
私有的聲明
標准中注冊的聲明(建議但不強制使用):
| 荷載部位的key | 描述 |
|---|---|
| iss | JWT簽發者(服務端) |
|
lat
|
JWT的簽發時間 |
| sub | JWT所面向的用戶 |
| aud | 接收JWT的一方 |
| exp | JWT的過期時間,該時間必須大於簽發時間 |
| nbf | 再某一時間段之前,該JWT不可用 |
| jti | JWT的唯一身份標識,主要用作一次性token,回避時序攻擊 |
公共的聲明:
公共的聲明可以添加任何的信息,一般添加用戶的相關信息或其他業務需要的必要信息,但不建議添加敏感信息,因為該部分在客戶端可解密。
私有的聲明:
私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味着該部分信息可以歸類為明文信息。
如下,定義一個payload:
{
"id":"1001",
"name":"yunya",
"age":"12",
}
然后將其進行base64加密,得到JWT的第二部分。
7B226964223A202231303031222C20226E616D65223A202279756E7961222C2022616765223A20223138227D
signature
JWT的第三部分是一個簽證信息,這個簽證信息由三部分組成
header (base64后的)
payload (base64后的)
secret
這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret組合加密,然后就構成了JWT的第三部分。
signature = hashlib.sha256()
signature.update(header_payload_result)
signature.update("salt".encode("utf-8")) # 加鹽
signature_result = signature.hexdigest() # 獲得結果
JWT第三部分結果:
3495bb47139c954ba17c3081262c55d2b436346e0d58990202cc668b7bca54ac
最終的JWT
下面將用Python進行演示,如何創建一個JWT:
import base64
import hashlib
import json
# 第一步:進行頭部聲明
header = {
"type":"JWT",
"alg":"HASH256"
}
header_result = base64.b64encode(json.dumps(header).encode("utf-8"))
# 第二步:進行荷載聲明,不要存放密碼等敏感信息,因為可通過反解出來(你也可以存放過期時間等,都是OK的)
payload = {
"id":"1001",
"name":"yunya",
"age":"18",
}
payload_result = base64.b16encode(json.dumps(payload).encode("utf-8"))
# 第三步:將頭部與荷載進行 . 拼接
header_payload_result = header_result + b"." + payload_result
# 第四步:將頭部與荷載的信息與鹽進行hash256加密(通常加密方式都是再alg中聲明的),得到簽證
signature = hashlib.sha256()
signature.update(header_payload_result)
signature.update("salt".encode("utf-8")) # 加鹽
signature_result = signature.hexdigest() # 獲得結果
# 第五步:通過 . 拼接出jwt
jwt = header_payload_result + b"." + signature_result.encode("utf-8")
print(jwt)
# b'eyJ0eXBlIjogIkpXVCIsICJhbGciOiAiSEFTSDI1NiJ9.7B226964223A202231303031222C20226E616D65223A202279756E7961222C2022616765223A20223138227D.3495bb47139c954ba17c3081262c55d2b436346e0d58990202cc668b7bca54ac'
驗證流程
下面是JWT的驗證流程:

再附上一張原生token驗證的流程:

優勢所在
要想了解JWT的優勢,則需要與cookie以及session做對比。
cookie的劣勢主要是存儲時不安全,所有數據存放只用戶本地,一旦被竊取就可以偽造登錄。
session的劣勢主要有三點:①.數據存放至服務器,占用服務器資源。②.每次用戶登錄成功后,都需要向數據庫中寫入session,速度緩慢。③.對於集群式的部署,如果存儲session的數據庫不一致,則會是個大麻煩,因為用戶如果接入了不同的服務器,則意味着寫入的session也在不同的數據庫中。這會導致用戶的session在不同數據庫中會存在多次寫入的問題。
了解了cookie以及session的劣勢后,jwt的優勢就顯而易見。
用戶數據存放至本地,但必須要與服務端存儲的鹽進行對比一致后才認證成功。
不需要有數據庫寫入的操作。
集群式部署時也沒有任何問題,前提是每個服務器的鹽都一樣。
