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簽發者(服務端) |
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
的優勢就顯而易見。
使用者資料存放至本地,但必須要與服務端儲存的鹽進行對比一致後才認證成功。
不需要有資料庫寫入的操作。
叢集式部署時也沒有任何問題,前提是每個伺服器的鹽都一樣。