1. 程式人生 > 實用技巧 >Django JWT登入認證機制

Django JWT登入認證機制

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表中去查詢使用者資料的。