python-JWT(Json Web Token)-pyjwt


JWT的引入

傳統登錄認證流程: 

1. 用戶第一次登錄時, 生成一個session並返回給前台, 同時將其與用戶主鍵一同存在后台服務器上(數據庫或緩存中)
2. 下一次訪問需要登錄的頁面時, 將session一起傳入
3. 后台拿着session去數據庫或緩存中查找是否存在該session, 存在則認證通過, 否則認證不通過

傳統認證的缺點:

1. session存在后台, 增加了存儲和讀取的開銷
2. 當存在多個后台服務器時, 需同步共享session, 比較麻煩

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) 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM