1. 程式人生 > 實用技巧 >python-JWT(Json Web Token)-pyjwt

python-JWT(Json Web Token)-pyjwt

JWT的引入

傳統登入認證流程:

1. 使用者第一次登入時, 生成一個token並返回給前臺, 同時將其與使用者主鍵一同存在後臺伺服器上(資料庫或快取中)
2. 下一次訪問需要登入的頁面時, 將token一起傳入
3. 後臺拿著token去資料庫或快取中查詢是否存在該token, 存在則認證通過, 否則認證不通過

傳統認證的缺點:

1. token存在後臺, 增加了儲存和讀取的開銷
2. 當存在多個後臺伺服器時, 需同步共享token, 比較麻煩

JWT認證流程(解決了傳統認證的問題):

1. 使用者第一次登入時, 生成一個token, 但後臺不儲存該token

2. 下一次訪問需要登入的頁面時, 將token一起傳入

3. 後臺拿著token進行解析和校驗, 若解析成功則認證通過, 否則認證不通過

JWT加密原理:

生成的token分為三個部分: HEADER.PAYLOAD.SIGNATURE, 這三個部分都是可逆演算法base64加密後的字串, 最後用點號(.)拼接.如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1. HEADER

代表了加密演算法和token型別, 若不顯示指定, 預設為:

{
  "alg": "HS256",
  "typ": "JWT"
}
加密後結果為: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. PAYLOAD

代表了想要傳輸的業務資訊和token的過期時間(可選), 例如:
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 451154141
}
加密後結果為: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

3. SIGNATURE

JWT的關鍵, 其規則是將前面兩段加密後的密文再加上自定義的鹽值一起拼接後,再通過不可逆演算法HS256(具體使用的是HEADER中的演算法)進行加密, 最後再對該密文進行可逆演算法base64加密

鹽值(salt): 指的是加密時加入的自定義的字串, 最好是隨機或者雜亂的字串, 這樣更能夠確定加密後字串的唯一性,django中可以使用settings中的SECRET_KEY

加密後結果為: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT解密原理:

拿到請求中傳過來的token後:
1. 按.號拆分token, 拿到三段值
2. 對這三段密文進行base64解密, 從明文中拿到加密演算法和業務資料以及過期時間
3. 再次拼接前兩段的密文和自定義的鹽值(該鹽值必須和建立token時的鹽值一樣), 使用HEADER中的演算法進行加密
4. 將加密的結果和拿到的token中的第三段解密的明文進行比較, 若完全一致則說明認證成功, 若不一致則說明token被篡改過, 認證失敗

對JWT三段式的思考:

JWT的根本思想就是將業務資料通過不可逆演算法加密儲存在token中, 那麼為什麼要搞成三段式這麼複雜呢?
直接把業務資料加上鹽值, 然後用預設不可逆演算法生成一段密文字串進行傳輸不就可以了嗎?
這樣加密時是比較簡單, 但是解密時卻由於不可逆演算法而拿不到其中的業務資料, 所以確實需要再加一段式來單獨儲存業務資料
JWT又加了一段用來儲存加密演算法, 能夠讓使用者自己確定具體使用什麼演算法進行加密, 增加了可擴充套件性

python中使用JWT

pyjwt

這是python使用JWT的基礎包, 在jwt官網中python語言點贊最多的就是pyjwt, 安裝方式為:pip install pyjwt, 這個包已經把加密和解密的邏輯寫好了, 我們只需要傳入加密演算法/業務資料/鹽值即可

在rest_framework中使用pyjwt

定義兩個介面, 登入(login)和檢視訂單(order), 只有登入過的使用者才能成功訪問檢視訂單介面, 我們可以在登入介面中若成功登入則返回jwt的加密token, 在訂單介面中自定義一個認證類, 在認證類中校驗token

1. 編輯urls.py

from django.urls import path
from users import views

urlpatterns = [
    path('login/', views.LoginView.as_view()),
    path('order/', views.OrderView.as_view()),
]

2. 編輯登入和訂單檢視類

1. 登入成功後, 呼叫獲取token的方法 create_token() , 傳入引數為使用者資訊和token過期時間(單位: 分鐘), 預設1分鐘

2. 在訂單檢視類中設定認證類JWTAuthentication

3.create_token和JWTAuthentication都定義在utils包的JWTAuth.py中

from rest_framework.views import APIView
from rest_framework.response import Response
from utils.JWTAuth import create_token, JWTAuthentication

class
LoginView(APIView): def post(self, request, *args, **kwargs): # 獲取使用者名稱密碼 name = request.data.get('name') pwd = request.data.get('pwd') # 獲取User物件 try: user = models.User.objects.filter(name=name, pwd=pwd).first() except Exception: return Response({'status': 1, 'errmsg': '使用者名稱或密碼不正確!'}) # 獲取token token = create_token({'id': user.id, 'name': user.name}, 1) # 返回成功響應 return Response({'status': 0, 'token': token}) class OrderView(APIView): authentication_classes = [JWTAuthentication, ] def get(self, request): return Response({'status': 0, 'msg': 'ok'})

3. 編輯JWTAuth.py

import jwt
from jwt import exceptions as JWTException
from django.conf import settings
import datetime
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

def create_token(payload, timeout=1):
    # 給傳過來的業務資料增加一個過期時間限制
    payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=timeout)
    # 定義鹽值
    salt = settings.SECRET_KEY
    # 預設不可逆加密演算法為HS256
    token = jwt.encode(payload=payload, key=salt)
    return token

class JWTAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 從url引數中獲取token
        token = request.query_params.get('token')
        # 鹽值
        salt = settings.SECRET_KEY
        # 解碼token
        try:
            payload = jwt.decode(jwt=token, key=salt, verify=True)
        except JWTException.ExpiredSignature:
            raise AuthenticationFailed('token已失效')
        except jwt.DecodeError:
            raise AuthenticationFailed('token認證失敗')
        except jwt.InvalidTokenError:
            raise AuthenticationFailed('非法的token')

        return payload.get('name'), token

注意: 設定過期時間時, 一定是在payload段中設定, 且鍵名固定為'exp', 值為datetime.datetime.utcnow() + datetime.timedelta(xxxx)