JWT認(rèn)證進(jìn)階
【0】準(zhǔn)備工作
(1)模型準(zhǔn)備
- 模型準(zhǔn)備(繼承
django
的auth_user
表)
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
mobile = models.CharField(max_length=11, verbose_name='手機(jī)號碼')
- 添加配置文件,修改用戶模型表為自定義表
AUTH_USER_MODEL = 'app名稱.UserInfo'
(2)知識點綁定方法
- 詳情請見:Python 面向?qū)ο笾壎ê头墙壎ǚ椒╛python 綁定方法-CSDN博客
- 實例方法
- 當(dāng)對象調(diào)用實例方法時(
對象.實例方法()
),自動將對象當(dāng)作第一個參數(shù)傳入 - 當(dāng)類調(diào)用實例方法時(
類.實例方法(類())
),需要手動傳入一個實例
- 當(dāng)對象調(diào)用實例方法時(
- 類方法
- 當(dāng)類調(diào)用類方法時(
類.類方法()
),自動將類當(dāng)作第一個參數(shù)傳入 - 當(dāng)對象調(diào)用類方法時(
對象.類方法()
),自動將對象的類當(dāng)作第一個參數(shù)傳入
- 當(dāng)類調(diào)用類方法時(
- 簡單示例
class A:
def __init__(self, name):
self.name = name
@classmethod
def hi(cls):
print(f"{cls.__name__} say hi")
def hello(self):
print(f"{self.name} say hello")
a = A('bruce')
# 實例調(diào)用類方法,自定將類傳入
a.hi()
# 類調(diào)用實例方法,需要手動將實例對象傳入
A.hello(a)
(3)保存信息顯示中文
- 在配置文件中注冊app就可以
INSTALLED_APPS = [
'rest_framework_simplejwt',
]
【1】jwt配置文件
(1)整體查看
- 這些內(nèi)容都在
from rest_framework_simplejwt import settings
可以自己去查看
DEFAULTS = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),# 訪問令牌的有效期。這里設(shè)置為5分鐘。
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),# 刷新令牌的有效期。這里設(shè)置為1天。
"ROTATE_REFRESH_TOKENS": False,# 是否每次使用刷新令牌時都生成新的刷新令牌。這里設(shè)置為False。
"BLACKLIST_AFTER_ROTATION": False,# 刷新令牌在旋轉(zhuǎn)后是否應(yīng)該被加入黑名單。這里設(shè)置為False。
"UPDATE_LAST_LOGIN": False,# 是否在每次驗證令牌時更新用戶的最后登錄時間。這里設(shè)置為False。
"ALGORITHM": "HS256",# 用于簽名的算法。這里使用的是HS256。
"SIGNING_KEY": settings.SECRET_KEY,# 用于簽名的密鑰。這里使用的是Django的SECRET_KEY。
"VERIFYING_KEY": "",# 用于驗證令牌的密鑰。這里沒有設(shè)置。
"AUDIENCE": None,# JWT的接收者。這里沒有設(shè)置。
"ISSUER": None,# JWT的簽發(fā)者。這里沒有設(shè)置。
"JSON_ENCODER": None,# 用于序列化JWT負(fù)載的JSON編碼器。這里沒有設(shè)置。
"JWK_URL": None,# JSON Web Key Set的URL,用于公鑰檢索。這里沒有設(shè)置。
"LEEWAY": 0,# 在驗證JWT的時間戳?xí)r允許的時間誤差(秒)。這里設(shè)置為0。
"AUTH_HEADER_TYPES": ("Bearer",),# 認(rèn)證頭中可以接受的令牌類型。這里設(shè)置為只接受"Bearer"類型。
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",# 認(rèn)證頭的名稱。這里設(shè)置
"USER_ID_FIELD": "id",# 用戶的ID字段名。這里設(shè)置為"id"。
"USER_ID_CLAIM": "user_id",# JWT中用戶ID的聲明名稱。這里設(shè)置為"user_id"。
"USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",# 用于驗證用戶的規(guī)則。這里使用的是默認(rèn)規(guī)則。
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),# l令牌類。這里設(shè)置為只使用訪問令牌。
"TOKEN_TYPE_CLAIM": "token_type",# 用于表示用戶的模型類。這里使用的是默認(rèn)模型。
"JTI_CLAIM": "jti",# JWT的JTI(JWT ID)聲明名稱。這里設(shè)置為"jti"。
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",# 用于表示用戶的模型類。這里使用的是默認(rèn)模型。
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",# 滑動刷新令牌中的過期聲明名稱。這里設(shè)置為"refresh_exp"。
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),# 滑動訪問令牌的有效期。這里設(shè)置為5分鐘。
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),# 滑動刷新令牌的有效期。這里設(shè)置為1天。
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",# 用于獲取令牌的序列化器。這里使用默認(rèn)的序列化器。
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",# 用于刷新令牌的序列化器。這里使用默認(rèn)的序列化器。
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",# 用于驗證令牌的序列化器。這里使用默認(rèn)的序列化器。
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",# 用于黑名單令牌的序列化器。這里使用默認(rèn)的序列化器。
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",# 用于獲取滑動令牌的序列化器。這里使用默認(rèn)的序列化器。
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",# 用于刷新滑動令牌的序列化器。這里使用默認(rèn)的序列化器。
"CHECK_REVOKE_TOKEN": False,# 是否檢查令牌是否已被撤銷。這里設(shè)置為False。
"REVOKE_TOKEN_CLAIM": "hash_password",# 用于撤銷令牌的聲明名稱。這里設(shè)置為"hash_password"。
}
(2)常用的配置
-
ACCESS_TOKEN_LIFETIME: 訪問令牌的有效期。這里設(shè)置為5分鐘。
-
REFRESH_TOKEN_LIFETIME: 刷新令牌的有效期。這里設(shè)置為1天。
-
TOKEN_OBTAIN_SERIALIZER:可自定義登錄的序列化器
【2】定制登錄返回格式
(1)定制整體返回格式
- jwt默認(rèn)返回格式是一個字典,包含兩個鍵值對
- 一個是刷新相關(guān)的token,另一個是登錄相關(guān)的token
- 我們期望的格式是一個大字典,包含返回自定義狀態(tài)碼
code
、描述信息msg
、用戶名username
和默認(rèn)的兩個token
- 所以需要我們自定義,這里的方法是替換默認(rèn)的登錄序列化類,自定義序列化類以實現(xiàn)自定義的返回整體格式
- 通過默認(rèn)配置:
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer"
- 可知道這個類是負(fù)責(zé)登錄序列化的,為了方便,繼承它,重寫它的方法,添加自定義的屬性
- 通過簡單觀察源碼發(fā)現(xiàn),返回的內(nèi)容就是
validate
里面的data
,所以只需要在這個data中添加修改一下字段就可以
- 代碼
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class JWTTokenPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
data = super().validate(attrs)
new_data = {
"code": 100,
"msg": '登錄驗證成功',
"username": self.user.username,
"refresh": data.get("refresh"),
"access": data.get("access"),
}
return new_data
# 配置文件替換默認(rèn)的序列化類
SIMPLE_JWT ={
'TOKEN_OBTAIN_SERIALIZER':'自定義序列化類的位置.JWTTokenPairSerializer'
}
# 使用默認(rèn)的JWT登錄
from django.urls import path
from rest_framework_simplejwt.views import token_obtain_pair
urlpatterns = [
path('v0/login/', token_obtain_pair),
]
(2)定制payload載荷
-
默認(rèn)載荷部分包含了
-
token_type
表示這是一個訪問令牌(access token) -
exp
是令牌的過期時間(UNIX時間戳) -
iat
是令牌的簽發(fā)時間 -
jti
是令牌的唯一標(biāo)識符 -
user_id
是用戶ID
-
-
現(xiàn)在思考如何添加自定義字段,比如用戶的用戶名
- 還是通過一步一步的找token,最終找到了載荷payload,是一個字典所以可以嘗試,像普通字典一樣添加新的信息
- 注意:這里面雖然有類方法,但是無論是self還是super都是實例對象,可以直接調(diào)用類方法
- 代碼:還是修改序列化類
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class JWTTokenPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
token['username'] = user.username
return token
- 檢查,添加成功
import base64
s = 'token第二段==='
res = base64.b64decode(s.encode('utf8'))
# b'{"token_type":"access","exp":1713528635,"iat":1713528335,"jti":"cc57595cd70f4938baa4673a6f1538ad","user_id":1}
# b'{"token_type":"access","exp":1713530348,"iat":1713530048,"jti":"6807d0722efb41bba01742a44767c18d","user_id":1,"username":"admin"}'
【3】多方式登錄
(1)簡介
- 實際的案例中,登錄的方式有很多種,既可以是用戶名,還可以是手機(jī)號,還可以是郵箱等,并且他們的登錄輸入入口都是一個,所以這里將實現(xiàn)這種接口
(2)登錄視圖函數(shù)和路由
- 序列化類有檢驗功能,所以重心不在這里
# 路由層
from django.urls import path, include
from rest_framework.routers import SimpleRouter
from .views import JWTLoginAPIView
router = SimpleRouter()
router.register('', JWTLoginAPIView, basename='login')
urlpatterns = [
path('v1/', include(router.urls)),
]
# 視圖層
from rest_framework.viewsets import GenericViewSet
from .serializer import JWTTokenSerializer
from rest_framework.response import Response
from rest_framework.decorators import action
class JWTLoginAPIView(GenericViewSet):
serializer_class = JWTTokenSerializer
@action(methods=['post'], detail=False)
def login(self, request, *args, **kwargs):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
refresh = ser.context.get('refresh')
access = ser.context.get('access')
return Response({"code": 100, "msg": "登錄驗證成功", "refresh": refresh, "access": access})
return Response({"code": 100, "msg": ser.erros})
(3)序列化類
- 序列化類的檢驗分為三個過程,首先是字段參數(shù)(包括validators),然后是局部鉤子,最后是全局鉤子
- 由于需要校驗用戶輸入字段和密碼字段,所以使用全局鉤子
- 整體邏輯
- 使用不同方法區(qū)分傳入的值是電話,還是郵箱,還是手機(jī)號
- 由于使用的是
django
的用戶表,所以要用相應(yīng)的加密方法判斷密碼 - 在定制載荷payload中,知道了
RefreshToken
處理token,所以要調(diào)用這個類的類方法for_user
處理
import re
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework_simplejwt.tokens import RefreshToken
from .models import UserInfo
class JWTTokenSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
if re.match(r'^1[3-9][0-9]{9}$', username):
user = UserInfo.objects.filter(username=username).first()
elif re.match(r"/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/", username):
user = UserInfo.objects.filter(username=username).first()
else:
user = UserInfo.objects.filter(username=username).first()
# 使用的是django的表,需要使用響應(yīng)的方法進(jìn)行密碼檢驗
if user and user.check_password(password):
# 獲取token
token = RefreshToken.for_user(user)
self.context['refresh'] = str(token)
self.context['access'] = str(token.access_token)
return attrs
raise ValidationError("用戶名或者密碼錯誤")
【4】自定義用戶表、手動簽發(fā)和認(rèn)證
(1)模型表
- 用戶表用普通的表
- 創(chuàng)建一個數(shù)據(jù)表用于側(cè)式認(rèn)證功能
from django.db import models
class UserNormal(models.Model):
username = models.CharField(max_length=64, verbose_name='用戶名')
password = models.CharField(max_length=64, verbose_name='密碼')
class Book(models.Model):
name = models.CharField(max_length=64, verbose_name="書名")
price = models.CharField(max_length=64, verbose_name="價格")
publish = models.CharField(max_length=64, verbose_name="出版社")
(2)路由層
- 自動路由
from django.urls import path, include
from rest_framework.routers import SimpleRouter
from .views import JWTLoginAPIView, BookAPIView
router = SimpleRouter()
router.register('', JWTLoginAPIView, basename='login')
router.register('book', BookAPIView, basename='book')
urlpatterns = [
path('v1/', include(router.urls)),
]
(3)視圖層
- 和之前沒有區(qū)別,多創(chuàng)一個視圖類用于測試
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from .serializer import JWTTokenSerializer, BookModelSerializer
from rest_framework.response import Response
from rest_framework.decorators import action
from .models import Book
from .authentication import JWTAuth
class BookAPIView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookModelSerializer
authentication_classes = [JWTAuth]
class JWTLoginAPIView(GenericViewSet):
serializer_class = JWTTokenSerializer
@action(methods=['post'], detail=False)
def login(self, request):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
refresh = ser.context.get('refresh')
access = ser.context.get('access')
return Response({"code": 100, "msg": "登錄驗證成功", "refresh": refresh, "access": access})
return Response({"code": 100, "msg": ser.errors})
(4)序列化
import re
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework_simplejwt.tokens import RefreshToken
from .models import UserInfo, Book, UserNormal
class BookModelSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
class JWTTokenSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
user = UserNormal.objects.filter(username=username, password=password).first()
if user:
# 獲取token
token = RefreshToken.for_user(user)
self.context['access'] = str(token.access_token)
self.context['refresh'] = str(token)
return attrs
raise ValidationError("用戶名或者密碼錯誤")
(5)認(rèn)證類
from rest_framework_simplejwt.authentication import JWTAuthentication
from .models import UserNormal
from rest_framework.exceptions import AuthenticationFailed
class JWTAuth(JWTAuthentication):
def authenticate(self, request):
token = request.META.get("HTTP_AUTHORIZATION")
if token:
# 調(diào)用父類的方法驗證token,內(nèi)部會自動驗證拋出異常
validated_token = super().get_validated_token(token)
# 放入的默認(rèn)信息是user_id
user_id = validated_token.get('user_id')
user = UserNormal.objects.filter(pk=user_id)
return user, token
else:
raise AuthenticationFailed("登錄認(rèn)證信息認(rèn)證失敗")
(6)測試
- 注意:上面調(diào)用的方法,不可以在token的前面帶任何參數(shù)
文章來源:http://www.zghlxwxcb.cn/news/detail-859764.html
- 解決辦法:
- 使用父類的獲取原生token方法,這樣仍將攜帶配置文件中的要求(Bearer,也可以自定義其他的)
header = self.get_header(request)
token = self.get_raw_token(header)
文章來源地址http://www.zghlxwxcb.cn/news/detail-859764.html
到了這里,關(guān)于DRF JWT認(rèn)證進(jìn)階的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!