Django REST framework JWT
在用戶注冊或登錄后,我們想記錄用戶的登錄狀態,或者為用戶創建身份認證的憑證。我們不再使用Session認證機制,而使用Json Web Token認證機制。
很多公司開發的一些移動端可能不支持cookie,並且我們通過cookie和session做接口登錄認證的話,效率其實並不是很高,我們的接口可能提供給多個客戶端,session數據保存在服務端,那么就需要每次都調用session數據進行驗證,比較耗時,所以引入了token認證的概念,我們也可以通過token來完成,我們來看看jwt是怎么玩的。
Json web token (JWT), 是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標准((RFC 7519).該token被設計為緊湊且安全的,特別適用於分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
看圖
JWT的構成
JWT就一段字符串,由三段信息構成的,將這三段信息文本用.
鏈接一起就構成了Jwt字符串。就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似於飛機上承載的物品),第三部分是簽證(signature).
header
jwt的頭部承載兩部分信息:
- 聲明類型,這里是jwt
- 聲明加密的算法 通常直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后將頭部進行base64.b64encode()加密(該加密是可以對稱解密的),構成了第一部分.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
python中base64加密解密
import base64
str1 = 'admin'
str2 = str1.encode()
b1 = base64.b64encode(str2) #數據越多,加密后的字符串越長
b2 = base64.b64decode(b1) #admin
各個語言中都有base64加密解密的功能,所以我們jwt為了安全,需要配合第三段加密
payload
載荷就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息可以存放下面三個部分信息。
- 標准中注冊的聲明
- 公共的聲明
- 私有的聲明
標准中注冊的聲明 (建議但不強制使用) :
-
iss: jwt簽發者
-
sub: jwt所面向的用戶
-
aud: 接收jwt的一方
-
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
-
nbf: 定義在什么時間之前,該jwt都是不可用的.
-
iat: jwt的簽發時間
-
jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
以上是JWT 規定的7個官方字段,供選用
公共的聲明 : 公共的聲明可以添加任何的信息,一般添加用戶的相關信息或其他業務需要的必要信息.但不建議添加敏感信息,因為該部分在客戶端可解密.
私有的聲明 : 私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味着該部分信息可以歸類為明文信息。
定義一個payload,json格式的數據:
{
"sub": "1234567890",
"exp": "3422335555", #時間戳形式
"name": "John Doe",
"admin": true
}
然后將其進行base64.b64encode() 加密,得到JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature
JWT的第三部分是一個簽證信息,這個簽證信息由三部分組成:
- header (base64后的)
- payload (base64后的)
- secret密鑰
這個部分需要base64加密后的header和base64加密后的payload使用.
連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret
組合加密,然后就構成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
將這三部分用.
連接成一個完整的字符串,構成了最終的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是可以自我簽發jwt了。
jwt的優點:
1. 實現分布式的單點登陸非常方便
2. 數據實際保存在客戶端,所以我們可以分擔都武器的存儲壓力
3. JWT不僅可用於認證,還可用於信息交換。善用JWT有助於減少服務器請求數據庫的次數,jwt的構成非常簡單,字節占用很小,所以它是非常便於傳輸的。
jwt的缺點:
1. 數據保存在了客戶端,我們服務端只認jwt,不識別客戶端。
2. jwt可以設置過期時間,但是因為數據保存在了客戶端,所以對於過期時間不好調整。
認證流程圖
關於簽發和核驗JWT,我們可以使用Django REST framework JWT擴展來完成。
文檔網站http://getblimp.github.io/django-rest-framework-jwt/
安裝配置JWT
安裝
pip install djangorestframework-jwt -i https://mirrors.aliyun.com/pypi/simple/
配置(github網址:https://github.com/jpadilla/django-rest-framework-jwt)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
- JWT_EXPIRATION_DELTA 指明token的有效期
我們django創建項目的時候,在settings配置文件中直接就給生成了一個serect_key,我們直接可以使用它作為我們jwt的serect_key,其實djangorestframework-jwt默認配置中就使用的它。
手動生成jwt(暫時用不到)
Django REST framework JWT 擴展的說明文檔中提供了手動簽發JWT的方法
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的視圖,可以直接使用
在子應用路由urls.py中
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path(r'login/', obtain_jwt_token),
]
在主路由中,引入當前子應用的路由文件
urlpatterns = [
...
path('user/', include("users.urls")),
# include 的值必須是 模塊名.urls 格式,字符串中間只能出現一個圓點
]
接下來,我們可以通過postman來測試下功能,但是jwt是通過username和password來進行登錄認證處理的,所以我們要給真實數據,jwt會去我們配置的user表中去查詢用戶數據的。