DRF JWT認證(二)

来源:https://www.cnblogs.com/48xz/archive/2022/04/10/16128119.html
-Advertisement-
Play Games

快速上手JWT簽發token和認證,有這一篇就夠了,DRF自帶的和自定義的都幫你總結好了,拿去用~ ...


目錄

DRF JWT認證(二)

img

上篇中對JWT有了基本的認知,這篇來略談JWT的使用

簽發:一般我們登錄成功後簽發一個token串,token串分為三段,頭部,載荷,簽名

1)用基本信息公司信息存儲json字典,採用base64演算法得到 頭字元串
2)用關鍵信息存儲json字典,採用base64演算法得到 荷載字元串,過期時間,用戶id,用戶名
3)用頭、體加密字元串通過加密演算法+秘鑰加密得到 簽名字元串
拼接成token返回給前臺

認證:根據客戶端帶token的請求 反解出 user 對象

1)將token按 . 拆分為三段字元串,第一段 頭部加密字元串 一般不需要做任何處理
2)第二段 體加密字元串,要反解出用戶主鍵,通過主鍵從User表中就能得到登錄用戶,過期時間是安全信息,確保token沒過期
3)再用 第一段 + 第二段 + 加密方式和秘鑰得到一個加密串,與第三段 簽名字元串 進行比較,通過後才能代表第二段校驗得到的user對象就是合法的登錄用戶

JWT可以使用如下兩種:

djangorestframework-jwtdjangorestframework-simplejwt

djangorestframework-jwthttps://github.com/jpadilla/django-rest-framework-jwt

djangorestframework-simplejwthttps://github.com/jazzband/djangorestframework-simplejwt

區別https://blog.csdn.net/lady_killer9/article/details/103075076

官網文檔https://jpadilla.github.io/django-rest-framework-jwt/

django中快速使用JWT

導入pip3 install djangorestframework-jwt

如何簽發?

步驟

  1. 路由中配置

    from rest_framework_jwt.views import obtain_jwt_token
    urlpatterns = [
        path('login/', obtain_jwt_token),
    ]
    
  2. 使用介面測試工具發送post請求到後端,就能基於auth的user表簽發token

    {
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTUyNDY2MiwiZW1haWwiOiIifQ.P1Y8Z3WhdndHoWE0PjW-ygd53Ng0T46U04oY8_0StwI"
    }
    

image

base64反解

import base64

# 第一段
s1 = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'
print(base64.b64decode(s1))
# b'{"typ":"JWT","alg":"HS256"}'

# 第二段
s2 = b'eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTUyNDY2MiwiZW1haWwiOiIifQ=='
print(base64.b64decode(s2))
# b'{"user_id":1,"username":"Hammer","exp":1649524662,"email":""}'
# 我們發現第二段可以反解密出用戶信息,是有一定的風險,可以使用,但是不能更改,就好比你的身份證丟了,別人可以在你不掛失的情況下去網吧上網



'''第三段不能不能反解,只能做base64解碼,第三段使用base64編碼只是為了統一格式'''

如何認證?

我們沒有認證的時候,直接訪問介面就可以返回數據,比如訪問/books/發送GET請求就可以獲取所有book信息,那麼現在添加認證,需要訪問通過才能訪問才更合理

步驟

  • 視圖中配置,必須配置認證類許可權類

  • 訪問需要在請求頭中使用,攜帶簽發的token串,格式是:

    key是Authorization
    value是jwt token串
    Authorization : jwt token串
    '''註意jwt和token串中間有空格'''
    

視圖

from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class BookView(GenericViewSet,ListModelMixin):
    ···
     # JSONWebTokenAuthentication :rest_framework_jwt模塊寫的認證類
    authentication_classes = [JSONWebTokenAuthentication,]
    # 需要配合一個許可權類
    permission_classes = [IsAuthenticated,]
    ···

image

定製簽發token返回格式

JWT預設的配置是,我們登錄成功後只返回一個token串,這也是預設的配置,我們如果想簽發token後返回更多數據需要我們自定製

步驟

  1. 寫一個函數,返回什麼格式,前端就能看見什麼格式
  2. 在配置文件中配置JWT_AUTH

utils.py

# 定義簽發token(登陸介面)返回格式
def jwt_response_payload_handler(token, user=None, request=None):
    return {
        'code': 100,
        'msg': "登陸成功",
        'token': token,
        'username': user.username
    }

settings.py

JWT_AUTH = {
      'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
  }

image


JWT源碼分析

簽發源碼分析

1.入口:path('login/', obtain_jwt_token)

2.obtain_jwt_token--->obtain_jwt_token = ObtainJSONWebToken.as_view()
ObtainJSONWebToken.as_view(),其實就是一個視圖類.as_view()

3.ObtainJSONWebToken類源碼
'''
class ObtainJSONWebToken(JSONWebTokenAPIView):
	serializer_class = JSONWebTokenSerializer
'''

4.登錄簽發token肯定需要一個post方法出來,但是ObtainJSONWebToken類內沒有父類JSONWebTokenAPIView寫了post方法:
    def post(self, request, *args, **kwargs):
        # 獲取數據:{'username': 'Hammer', 'password': '7410'}
        serializer = self.get_serializer(data=request.data)
		# 校驗
        if serializer.is_valid():
            user = serializer.object.get('user') or request.user # 獲取用戶
            token = serializer.object.get('token') # 獲取token
            response_data = jwt_response_payload_handler(token, user, request) 
           #  {'code': 100, 'msg': '登陸成功', 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTU4MTU0NiwiZW1haWwiOiIifQ.2oAjKQ90SV2S9Yxrwppo7BwAOv0xFW4i4AHHBX5Cg2Q', 'username': 'Hammer'}
            response = Response(response_data)
            if api_settings.JWT_AUTH_COOKIE:
               ···
            return response # 定製什麼返回什麼

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

5.get_serializer(data=request.data)如何獲取到用戶數據?
JSONWebTokenSerializer序列化類中全局鉤子中獲取當前登錄用戶和簽發token
···
payload = jwt_payload_handler(user)
                return {
                    'token': jwt_encode_handler(payload),
                    'user': user
                }
···

簽發總結

從obtain_jwt_token開始, 通過ObtainJSONWebToken視圖類處理,其實是父類JSONWebTokenAPIView的post方法通過傳入的用戶名和密碼處理獲取當前用戶,簽發了token


認證源碼分析

# 視圖類內認證類搭配許可權類使用
    authentication_classes = [JSONWebTokenAuthentication, ]
    permission_classes = [IsAuthenticated, ]

我們在前面寫過,如果需要認證肯定需要重寫authenticate方法,這裡從列表內的認證類作為入口分析:

'''認證類源碼'''
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
    www_authenticate_realm = 'api'

    def get_jwt_value(self, request):
        # 獲取傳入的Authorization:jwt token串,然後切分
        auth = get_authorization_header(request).split()
        auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
		# 獲取不到的情況
        if not auth:
            if api_settings.JWT_AUTH_COOKIE:
                return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
            return None  # 直接返回None,也不會報錯,所以必須搭配許可權類使用

        ···

        return auth[1]  # 一切符合判斷條件,通過split切分的列表索引到token串
'''認證類父類源碼'''
def authenticate(self, request):
        jwt_value = self.get_jwt_value(request) # 獲取真正的token,三段式,上面分析
        if jwt_value is None: # 如果沒傳token,就不認證了,直接通過,所以需要配合許可權類一起用
            return None

        try:
            payload = jwt_decode_handler(jwt_value)# 驗證簽名
        except jwt.ExpiredSignature:
            msg = _('Signature has expired.') # 過期了
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = _('Error decoding signature.')# 被篡改了
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()# 不知名的錯誤

        user = self.authenticate_credentials(payload)

        return (user, jwt_value)

簽發源碼內的其他兩個類

導入from rest_framework_jwt.views import obtain_jwt_token,refresh_jwt_token,verify_jwt_token

obtain_jwt_token = ObtainJSONWebToken.as_view()  # 獲取token
refresh_jwt_token = RefreshJSONWebToken.as_view()  # 更新token
verify_jwt_token = VerifyJSONWebToken.as_view()  # 認證token

refresh_jwt_token用法

# 配置文件
JWT_AUTH = {
    'JWT_ALLOW_REFRESH': True
}

# 路由
    path('refresh/', refresh_jwt_token)

image


verify_jwt_token用法

path('verify/', verify_jwt_token),

image


自定義User表,簽發token

普通寫法,視圖類寫

上面我們寫道,簽發token是基於Django自帶的auth_user表簽發,如果我們自定義User表該如何簽發token,如下:

視圖

# 自定義表簽發token
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings
from app01 import models
class UserView(ViewSetMixin,APIView):
    @action(methods=['POST'],detail=False)
    def login(self,request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = models.UserInfo.objects.filter(username=username,password=password).first()
        response_dict = {'code':None,'msg':None}
        # 源碼copy錯來使用
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        if user:
            '''
            簽發token去源碼copy過來使用
            '''
            # 載荷字典
            payload = jwt_payload_handler(user)
            print(payload)
            # {'user_id': 1, 'username': 'Hammer', 'exp': datetime.datetime(2022, 4, 10, 13, 13, 15, 363206), 'email': '[email protected]', 'orig_iat': 1649596095}
            # 通過荷載得到token串
            token = jwt_encode_handler(payload)
            response_dict['code'] = 2000
            response_dict['msg'] = '登錄成功'
            response_dict['token'] = token

        else:
            response_dict['code'] = 4001
            response_dict['msg'] = '登錄失敗,用戶名或密碼錯誤'
        return Response(response_dict)

模型

# user表
class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    email = models.EmailField()

路由

from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',views.UserView,'user')

image


序列化類中寫邏輯

源碼中簽發校驗都在序列化類中完成,這種寫法確實比較常用,我們來使用這種方式自定義,將上面視圖的校驗邏輯寫到序列化類中,這個序列化類只用來做反序列化,這樣我們就可以利用 反序列化 的欄位校驗功能來幫助我們校驗(模型中的條件),但是我們不做保存操作

視圖

from .serializer import UserInfoSerializer
class UserView(ViewSetMixin,APIView):
    @action(methods=['POST'],detail=False)
    def login(self,request):
        # 如果想獲取什麼這裡可以實例化對象寫入,比如request
        serializer = UserInfoSerializer(data=request.data, context={'request': request})
        response_dict = {'code':None,'msg':None}
        # 校驗,局部鉤子,全局鉤子都校驗完才算校驗通過,走自己的校驗規則
        if serializer.is_valid():
            # 從序列化器對象中獲取token和username
           token = serializer.context.get('token')
           username = serializer.context.get('username')

           response_dict['code']=2000
           response_dict['msg']='登錄成功'
           response_dict['token'] = token
           response_dict['username'] = username
        else:
            response_dict['code'] = 4001
            response_dict['msg'] = '登錄失敗,用戶名或密碼錯誤'

        return Response(response_dict)

序列化器

from rest_framework.exceptions import ValidationError


class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserInfo
        # 根據模型里的欄位寫
        fields = ['username', 'password']

    # 全局鉤子
    def validate(self, attrs):
        # attrs是校驗過的欄位,這裡利用
        username = attrs.get('username')
        password = attrs.get('password')
        user = UserInfo.objects.filter(username=username, password=password).first()

        from rest_framework_jwt.settings import api_settings
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

        if user:  # 登錄成功

            payload = jwt_payload_handler(user)  # 得到荷載字典
            token = jwt_encode_handler(payload)  # 通過荷載得到token串
            # 將token放入context字典中
            self.context['token'] = token
            self.context['username'] = username
            # context是serializer和視圖類溝通的橋梁
            print(self.context.get('request').method)
        else:  # 登錄失敗
            raise ValidationError('用戶名或密碼錯誤')
        return attrs

image

總結

需要我們註意的是,context只是我們定義的字典,比如上面寫到的實例化序列化類中指定的context,那麼就可以從序列化類列印出請求的方法,context是序列化類和視圖類溝通的橋梁


自定義認證類

auth.py

import jwt
from django.utils.translation import ugettext as _
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.settings import api_settings
from .models import UserInfo


class JWTAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 第一步、取出傳入的token,從請求頭中取

        # 這裡註意,獲取的時候格式為:HTTP_請求頭的key大寫
        jwt_value = request.META.get('HTTP_TOKEN')
        jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
        # 驗證token:驗證是否過期,是否被篡改,是否有其他未知錯誤,從源碼copy過來使用
        if jwt_value:
            try:
                payload = jwt_decode_handler(jwt_value)
            except jwt.ExpiredSignature:
                msg = _('Signature has expired.')
                raise exceptions.AuthenticationFailed(msg)
            except jwt.DecodeError:
                msg = _('Error decoding signature.')
                raise exceptions.AuthenticationFailed(msg)
            except jwt.InvalidTokenError:
                msg = _('Unknown Error.')
                raise exceptions.AuthenticationFailed(msg)

            # 第二部、通過payload獲得當前登錄用戶,本質是用戶信息通過base64編碼到token串的第二段載荷中
            user = UserInfo.objects.filter(pk=payload['user_id']).first()
            # 返回user和token
            return (user, jwt_value)
        else:
            raise AuthenticationFailed('No token was detected')

視圖

from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializer import BookSerializer
from .auth import JWTAuthentication
class BookView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    authentication_classes = [JWTAuthentication,]

序列化器

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

路由

from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('book',views.BookView,'book')

正常的情況

image

不攜帶token的情況

image


總結

  • 從請求頭中獲取token,格式是HTTP_KEY,key要大寫
  • 認證token串沒有問題,返回用戶信息從載荷中獲取,本質是用戶信息通過base64編碼到token串的第二段載荷中,可以通過base64解碼獲取到用戶信息

補充:HttpRequest.META

HTTP請求的數據在META中

HttpRequest.META

   一個標準的Python 字典,包含所有的HTTP 首部。具體的頭部信息取決於客戶端和伺服器,下麵是一些示例:
  取值:

    CONTENT_LENGTH —— 請求的正文的長度(是一個字元串)。
    CONTENT_TYPE —— 請求的正文的MIME 類型。
    HTTP_ACCEPT —— 響應可接收的Content-Type。
    HTTP_ACCEPT_ENCODING —— 響應可接收的編碼。
    HTTP_ACCEPT_LANGUAGE —— 響應可接收的語言。
    HTTP_HOST —— 客服端發送的HTTP Host 頭部。
    HTTP_REFERER —— Referring 頁面。
    HTTP_USER_AGENT —— 客戶端的user-agent 字元串。
    QUERY_STRING —— 單個字元串形式的查詢字元串(未解析過的形式)。
    REMOTE_ADDR —— 客戶端的IP 地址。
    REMOTE_HOST —— 客戶端的主機名。
    REMOTE_USER —— 伺服器認證後的用戶。
    REQUEST_METHOD —— 一個字元串,例如"GET" 或"POST"。
    SERVER_NAME —— 伺服器的主機名。
    SERVER_PORT —— 伺服器的埠(是一個字元串)。
   從上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,請求中的任何 HTTP 首部轉換為 META 的鍵時,
    都會將所有字母大寫並將連接符替換為下劃線最後加上 HTTP_  首碼。
    所以,一個叫做 X-Bender 的頭部將轉換成 META 中的 HTTP_X_BENDER 鍵。

*** 有錯請指正,感謝~
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • VUE生命周期函數 可謂是一個個鮮活的生命在服務於各個在使用VUE框架的碼農~ beforeCreate: 創建實例之前; 初始化 註入&校驗 把data、methods、props、computed、provide、watch...依次掛載到實例上 methods 中普通的方法和computed中 ...
  • 正文 1. 阿裡雲DataV 2. 積木報表jimureport 3. 百度Sugar 4. 帆軟 最經常的工作是將一些項目的數據從資料庫導出,然後分門別類的列到excel表格中,領導看起來眼花繚亂。 要是能以圖表可視化展現出來,領導就可以看到項目近幾個月的走勢,也知道之後要怎麼決策了。 嘗試了使用 ...
  • 前端周刊發表每周前端技術相關的大事件、文章教程、一些框架的版本更新、以及代碼和工具。每周定期發表,歡迎大家關註、轉載。 如果外鏈不能訪問,關註公眾號「前端每周看」,裡面有解決辦法 大事件 Veni,vidi,formatae! 宣佈Rome Formatter:超快速的 JavaScript 格式化 ...
  • 狀態模式(State Pattern)指允許一個對象在其內部狀態改變時改變它的行為,對象看起來似乎修改了它的類。 一般用來實現狀態機,而狀態機常用在游戲、工作流引擎等系統的開發中: 有限狀態機(Finite State Machine,FSM),狀態機有三個組成部分:狀態(State)、事件(Eve ...
  • 1 .解決跳轉問題:添加一個login方法,跳轉返回一個字元串。 中央控制器DispacherServlet調用EmpController,所以字元串返回給中央控制器。如下圖所示:中央控制器幫我們統一的做 資源的轉發(forward/include) 或 重定向。 1.1 更新 EmpControl ...
  • 在上次反思DDD實踐之後,在類目樹管理項目中再次實踐DDD。從需求分析到建模和具體的落地,結合個人體會,都是乾貨。 ...
  • 抽象工廠模式是什麼 抽象工廠是一種創建型設計模式,用於產品族的構建。它能創建一系列相關的對象, 而無需指定其具體類。 為什麼用抽象工廠模式 在工廠方法模式中具體工廠負責生產具體的產品,每一個具體工廠對應一種具體產品,工廠方法也具有唯一性,一般情況下,一個具體工廠中只有一個工廠方法。但是有時候我們需要 ...
  • Excelize 是 Go 語言編寫的用於操作 Office Excel 文檔基礎庫。2022年4月11日,社區正式發佈了 2.6.0 版本,該版本包含了多項新增功能、錯誤修複和相容性提升優化。下麵是有關該版本更新內容的摘要。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...