Django REST framework JWT
在用戶注冊或登錄后,我們想記錄用戶的登錄狀態,或者為用戶創建身份認證的憑證,我們不再使用Session認證機制,而使用Json Web Token認證機制,
Json web token (JWT), 是為了在網路應用環境間傳遞宣告而執行的一種基于JSON的開放標準((RFC 7519).該token被設計為緊湊且安全的,特別適用于分布式站點的單點登錄(SSO)場景,JWT的宣告一般被用來在身份提供者(客戶端)和服務提供者(服務端)間傳遞被認證的用戶身份資訊,以便于從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的宣告資訊,該token也可直接被用于身份認證,也可被資料加密傳輸,
JWT的構成
JWT就一段字串,由三段資訊構成的,將這三段資訊文本用.拼接一起就構成了Jwt token字串,就像這樣:
eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAicm9vdCIsICJleHAiOiAiMTUwMTIzNDU1IiwgImlhdCI6ICIxNTAxMDM0NTUiLCAibmFtZSI6ICJ3YW5neGlhb21pbmciLCAiYWRtaW4iOiB0cnVlLCAiYWNjX3B3ZCI6ICJRaUxDSmhiR2NpT2lKSVV6STFOaUo5UWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjkifQ==.815ce0e4e15fff813c5c9b66cfc3791c35745349f68530bc862f7f63c9553f4b
第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似于飛機上承載的物品),第三部分是簽證(signature).
header
jwt的頭部承載兩部分資訊:
- typ: 宣告token型別,這里是jwt ,typ的值也可以是:Bear
- alg: 宣告簽證的加密的演算法 通常直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后將頭部進行base64編碼,構成了jwt的第一部分頭部
python代碼舉例:
import base64, json
header_data = https://www.cnblogs.com/QiaoPengjun/p/{"typ": "jwt", "alg": "HS256"}
header = base64.b64encode( json.dumps(header_data).encode() ).decode()
print(header) # eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9
payload
載荷就是存放有效資訊的地方,這個名字像是特指飛機上承載的貨倉,這些有效資訊包含三個部分:
- 標準宣告
- 公共宣告
- 私有宣告
標準宣告指定jwt實作規范中要求的屬性, (建議但不強制使用) :
- iss: jwt簽發者
- sub: jwt所面向的用戶
- aud: 接收jwt的一方
- exp: jwt的過期時間,這個過期時間必須要大于簽發時間
- nbf: 定義在什么時間之前,該jwt都是不可用的.
- iat: jwt的簽發時間
- jti: jwt的唯一身份標識,主要用來作為一次性token, 從而回避重放攻擊,
公共宣告 : 公共的宣告可以添加任何的公開資訊,一般添加用戶的相關資訊或其他業務需要的必要資訊.但不建議添加敏感資訊,因為該部分在客戶端可解密.
私有宣告 : 私有宣告是提供者和消費者所共同定義的宣告,一般不建議存放敏感資訊,里面存放的是一些可以在服務端或者客戶端通過秘鑰進行加密和解密的加密資訊,往往采用的RSA非對稱加密演算法,
python舉例,定義一個payload,
import base64, json
from datetime import datetime
iat = int(datetime.now().timestamp())
payload_data = https://www.cnblogs.com/QiaoPengjun/p/{"sub": "root",
"exp": iat+3600, # 假設一小時過期
"iat": iat,
"name": "wangxiaoming",
"admin": True,
"acc_pwd": "QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9",
}
# 將其進行base64編碼,得到JWT的第二部分,
payload = base64.b64encode(json.dumps(payload_data).encode()).decode()
print(payload) # eyJzdWIiOiAicm9vdCIsICJleHAiOiAxNjMxNTI4NDQ4LCAiaWF0IjogMTYzMTUyNDg0OCwgIm5hbWUiOiAid2FuZ3hpYW9taW5nIiwgImFkbWluIjogdHJ1ZSwgImFjY19wd2QiOiAiUWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5In0=
signature
JWT的第三部分是一個簽證資訊,用于辨真偽,防篡改,這個簽證資訊由三部分組成:
-
header (base64后的頭部)
-
payload (base64后的載荷)
-
secret(保存在服務端的秘鑰字串,不會提供給客戶端的)
import base64, json, hashlib
if name == 'main':
"""jwt 頭部的生成"""
header_data = https://www.cnblogs.com/QiaoPengjun/p/{"typ": "jwt", "alg": "HS256"}
header = base64.b64encode( json.dumps(header_data).encode() ).decode()
print(header) # eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9"""jwt 載荷的生成""" payload_data = https://www.cnblogs.com/QiaoPengjun/p/{"sub": "root", "exp": "150123455", "iat": "150103455", "name": "wangxiaoming", "admin": True, "acc_pwd": "QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9", } # 將其進行base64編碼,得到JWT的第二部分, payload = base64.b64encode(json.dumps(payload_data).encode()).decode() print(payload) # eyJzdWIiOiAicm9vdCIsICJleHAiOiAiMTUwMTIzNDU1IiwgImlhdCI6ICIxNTAxMDM0NTUiLCAibmFtZSI6ICJ3YW5neGlhb21pbmciLCAiYWRtaW4iOiB0cnVlLCAiYWNjX3B3ZCI6ICJRaUxDSmhiR2NpT2lKSVV6STFOaUo5UWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjkifQ== # from django.conf import settings # secret = settings.SECRET_KEY secret = 'django-insecure-hbcv-y9ux0&8qhtkgmh1skvw#v7ru%t(z-#chw#9g5x1r3z=$p' data = https://www.cnblogs.com/QiaoPengjun/p/header + payload + secret # 秘鑰絕對不能提供給客戶端, HS256 = hashlib.sha256() HS256.update(data.encode('utf-8')) signature = HS256.hexdigest() print(signature) # 815ce0e4e15fff813c5c9b66cfc3791c35745349f68530bc862f7f63c9553f4b # jwt 最終的生成 token = f"{header}.{payload}.{signature}" print(token) # eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAicm9vdCIsICJleHAiOiAiMTUwMTIzNDU1IiwgImlhdCI6ICIxNTAxMDM0NTUiLCAibmFtZSI6ICJ3YW5neGlhb21pbmciLCAiYWRtaW4iOiB0cnVlLCAiYWNjX3B3ZCI6ICJRaUxDSmhiR2NpT2lKSVV6STFOaUo5UWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjkifQ==.815ce0e4e15fff813c5c9b66cfc3791c35745349f68530bc862f7f63c9553f4b
注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去,一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了,
python代碼舉例:
import base64, json, hashlib
from datetime import datetime
if __name__ == '__main__':
"""jwt 頭部的生成"""
header_data = https://www.cnblogs.com/QiaoPengjun/p/{"typ": "jwt", "alg": "HS256"}
header = base64.b64encode( json.dumps(header_data).encode() ).decode()
print(header) # eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9
"""jwt 載荷的生成"""
iat = int(datetime.now().timestamp())
payload_data = https://www.cnblogs.com/QiaoPengjun/p/{"sub": "root",
"exp": iat+3600, # 假設1小時過期
"iat": iat,
"name": "wangxiaoming",
"admin": True,
"acc_pwd": "QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9",
}
# 將其進行base64編碼,得到JWT的第二部分,
payload = base64.b64encode(json.dumps(payload_data).encode()).decode()
print(payload) # eyJzdWIiOiAicm9vdCIsICJleHAiOiAxNjMxNTI4NDQ4LCAiaWF0IjogMTYzMTUyNDg0OCwgIm5hbWUiOiAid2FuZ3hpYW9taW5nIiwgImFkbWluIjogdHJ1ZSwgImFjY19wd2QiOiAiUWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5In0=
# from django.conf import settings
# secret = settings.SECRET_KEY
SECRET_KEY = 'django-insecure-hbcv-y9ux0&8qhtkgmh1skvw#v7ru%t(z-#chw#9g5x1r3z=$p'
data = https://www.cnblogs.com/QiaoPengjun/p/header + payload + SECRET_KEY # 秘鑰絕對不能提供給客戶端,
HS256 = hashlib.sha256()
HS256.update(data.encode('utf-8'))
signature = HS256.hexdigest()
print(signature) # 815ce0e4e15fff813c5c9b66cfc3791c35745349f68530bc862f7f63c9553f4b
# jwt 最終的生成
token = f"{header}.{payload}.{signature}"
print(token)
# eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAicm9vdCIsICJleHAiOiAxNjMxNTI4NTA2LCAiaWF0IjogMTYzMTUyNDkwNiwgIm5hbWUiOiAid2FuZ3hpYW9taW5nIiwgImFkbWluIjogdHJ1ZSwgImFjY19wd2QiOiAiUWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5In0=.a8c677945fc277d8e677514420a1bff645da00498c2af8fb29189d652391a435
"""驗證邏輯"""
# token = "eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAicm9vdCIsICJleHAiOiAxNjMxNTI4NTA2LCAiaWF0IjogMTYzMTUyNDkwNiwgIm5hbWUiOiAid2FuZ3hpYW9taW5nIiwgImFkbWluIjogdHJ1ZSwgImFjY19wd2QiOiAiUWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5In0=.a8c677945fc277d8e677514420a1bff645da00498c2af8fb29189d652391a435"
# token = "eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAicm9vdCIsICJleHAiOiAxNjMxNTIxNzA5LCAiaWF0IjogMTYzMTUxODEwOSwgIm5hbWUiOiAid2FuZ3hpYW9taW5nIiwgImFkbWluIjogdHJ1ZSwgImFjY19wd2QiOiAiUWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5In0=.b82524a2155a24bcb4f085993545ec74766688849fd832e4edf9dd592167a2e3"
token = "eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiJyb290IiwiZXhwIjoxNjMxNTI5MDg4LCJpYXQiOjE2MzE1MjU0ODgsIm5hbWUiOiJ3YW5neGlhb2hvbmciLCJhZG1pbiI6dHJ1ZSwiYWNjX3B3ZCI6IlFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5UWlMQ0poYkdjaU9pSklVekkxTmlKOSJ9.b533c5515444c51058557017e433d411379862d91640c8beed6f2617b1da2feb"
header, payload, signature = token.split(".")
# 驗證是否過期了
payload_data = https://www.cnblogs.com/QiaoPengjun/p/json.loads( base64.b64decode(payload.encode()) )
exp = payload_data.get("exp",None)
if exp is not None and int(exp) < int(datetime.now().timestamp()):
raise Exception("token過期!!!")
# 驗證token是否有效,是否被串改
# from django.conf import settings
# secret = settings.SECRET_KEY
SECRET_KEY = 'django-insecure-hbcv-y9ux0&8qhtkgmh1skvw#v7ru%t(z-#chw#9g5x1r3z=$p' # 獲取原來的秘鑰
data = https://www.cnblogs.com/QiaoPengjun/p/header + payload + SECRET_KEY # 秘鑰絕對不能提供給客戶端,
HS256 = hashlib.sha256()
HS256.update(data.encode('utf-8'))
new_signature = HS256.hexdigest()
if new_signature != signature:
print("認證失敗")
else:
print("認證通過")
關于簽發和核驗JWT,我們可以使用Django REST framework JWT擴展來完成,
檔案網站:https://jpadilla.github.io/django-rest-framework-jwt/
安裝配置JWT
安裝
pip install djangorestframework-jwt
settings.dev,配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
import datetime
JWT_AUTH = {
# 設定jwt的有效期
'JWT_EXPIRATION_DELTA': datetime.timedelta(weeks=1), # 一周有效
}
- JWT_EXPIRATION_DELTA 指明token的有效期
生成jwt
Django REST framework JWT 擴展的說明檔案中提供了手動簽發JWT的方法
https://jpadilla.github.io/django-rest-framework-jwt/#creating-a-new-token-manually
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
在用戶注冊或登錄成功后,在序列化器中回傳用戶資訊以后同時回傳token即可,
后端實作登陸認證介面
Django REST framework JWT提供了登錄獲取token的視圖,可以直接使用它系結一個url地址即可,
在users子應用路由urls.py中
from rest_framework_jwt.views import obtain_jwt_token
from django.urls import path
urlpatterns = [
path('login/', obtain_jwt_token),
]
接下來,我們可以通過postman來測驗下功能,可以發送form表單,也可以發送json,username和password是必填欄位
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/457978.html
標籤:Python
