JWT(json-web-token) 詳解及應用
目錄
什么是JWT
Json web token(JWT)是為了網絡應用環境間傳遞聲明而執行的一種基於JSON的開發標准(RFC 7519),
該token被設計為緊湊且安全的,特別適用於分布式站點的單點登陸(SSO)場景。JWT的聲明一般被用來在
身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加
一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
一般用於用戶認證(前后端分離/微信小程序/app開發).
基於token的認證和傳統的Session認證的區別
# 傳統的session認證
http協議本身是一種無狀態的協議,而這就意味着如果用戶向我們的應用提供了用戶名和密碼來進行用戶認證,
那么下一次請求時,用戶還要再一次進行用戶認證才行,因為根據http協議,我們並不能知道是哪個用戶發送的請求,
所以為了讓我們的應用能識別是哪個用戶發出的,我們只能在服務器存儲一份用戶登陸的信息,
這份登陸信息會在響應時傳遞給服務器,告訴其保存為cookie,以便下次請求時發送給我們的應用,
這樣我們的英喲個就能識別請求來自哪個用戶了,這就是傳統的基於sessino認證,但是這種基於session
的認證使應用本身很難得擴展,隨着不用客戶端的增加,獨立的服務器已無法承載更多的用戶,
而這個時候基於session認證應用的問題就會暴露出來.
session:每個用戶經過我們的應用認證之后,我們的應用都要在服務端做一次記錄,以便用戶下次請求的鑒別,
通常而言session都是保存在內存中,而隨着認證用戶的增多,服務端的開銷會明顯增大.擴展性:用戶認證之后,
服務端做認證記錄,如果認證的記錄被保存在內存的話,這意味着用戶下次請求還必須要請求在這台服務器上,
這樣才能拿到授權的資源,這樣在分布式的應用上,響應的限制了負載均衡器的能力,也意味着限制了應用的擴展性
CSRF:因為是基於cookie來進行用戶識別的,cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊.
# 基於token的鑒權機制
基於token的鑒權機制類似於http協議也是無狀態的,它不需要在服務端去保留用戶的認證信息或會話信息。
這也就意味着機遇tokent認證機制的應用不需要去考慮用戶在哪一台服務器登陸了,這就為應用的擴展提供了便利.
- 流程是這樣的
用戶使用用戶名密碼請求服務器
服務器進行驗證用戶信息
服務器通過驗證發送給用戶一個token
客戶端存儲token,並在每次請求時附加這個token值
服務器驗證token,並返回數據
這個token必須要在每次請求時發送給服務器,它應該保存在請求頭中,另外,服務器要支持CORS
(跨來源資源共享)策略,一般我們在服務端這么做就可以了 Access-Control-Allow-Origin:*
傳統的token認證
用戶登錄成功后,服務端生成一個隨機token給用戶,並且在服務端(數據庫或緩存)中保存一份token,
以后用戶再來訪問時需攜帶token,服務端接收到token之后,去數據庫或緩存中進行校驗token的是否超時、是否合法。
JWT認證
用戶登錄,服務端給用戶返回一個token(服務端不保存).
以后用戶再來訪問,需要攜帶token,服務端獲取token后,再做token的校驗.
優勢:相較於傳統的token相比,它無需在服務端保存token.
JWT實現過程
-
JWT通常由三部分組成: 頭信息(header), 消息體(payload)和簽名(signature)。
# 用戶提交用戶名和密碼給服務端,如果登錄成功,使用jwt創建一個token,並給用戶返回(返回的token如下).
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
# 注意:jwt生成的token是由三段字符串組成,並且用.(點)連接起來.
# 第一段字符串,HEADER, 內部包含算法/token類型.
# json轉化成字符串,然后做 base64url 加密.(注意:base64url加密是先做base64加密,然后再將 - 替代 + 及 _ 替代 /)
{
"alg": "HS256",
"typ": "JWT"
}
# 這里的算法是可以改的
# 第二段字符串,payload,自定義值.
# json轉化成字符串,然后做 base64url 加密
{
"id": "123123",
"name": "chenggen",
"exp": 1516239022 # 超時時間
}
# 字典的第一第二鍵值對是用戶信息(可以根據需要使用用戶信息,注意避免使用用戶敏感信息如:密碼),exp是超時時間
# 第三段字符串:
第一步: 第1,2部分密文拼接起來
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
第二步:對前2部分密文進行HS256加密 + 加鹽
第三步:對HS256加密后的密文再做base64url加密
-
以后用戶再來訪問時候,需要攜帶token,后端需要對token進行校驗
# 第一步: 獲取token,對token進行切割
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
# 第二步: 對第二段進行base64url解密,並獲取payload信息,檢測token是否已經超時?
{
"id": "123123",
"name": "chenggen",
"exp": 1516239022 # 超時時間
}
# 第三步: 把第1,2端拼接,再次執行sha256加密
1: 第1,2部分密文拼接起來
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
2:對前2部分密文進行HS256加密 + 加鹽
密文 = base64解密(SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c)
如果相等,表示token未被修改過.(認證通過)
JWT應用示列
# 安裝 pip install pyjwt
pyjwt.encode 生成token
pyjwt.decode token解密
# jwt創建token代碼
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': 'gkf', # 自定義用戶名
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5) # 超時時間
}
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驗證token代碼
import jwt
from jwt import exceptions
SALT = 'iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv='
def get_payload(token):
"""
根據token獲取payload
:param token:
:return:SALT
"""
SALT = 'dsszfdfasfxz2123&*#^%&@&#*'
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)
-
drf-jwt 與 flask-jwt 代碼示例
鏈接:https://pan.baidu.com/s/1g9akaYauenK7nh_dnMON0A
提取碼:bnwf
擴展與總結
djangorestframework-jwt本質是調用pyjwt實現. pyjwt更好用,可以支持其他框架使用(一招鮮吃遍天).
優點:
因為json的通用性,所以JWT是可以跨語言支持的,像C#,JavaScript,NodeJS,PHP等許多語言都可以使用
因為由了payload部分,所以JWT可以在自身存儲一些其它業務邏輯所必要的非敏感信息,便於傳輸,
jwt的構成非常簡單,字節占用很小,所以它是非常便於傳輸的,它不需要在服務端保存會話信息,所以它易於應用的擴展.
安全相關:
不應該在jwt的payload部分存儲敏感信息,因為該部分是客戶端可解密的部分,保護好secret私鑰。該私鑰非常重要,如果可以請使用https協議