DRF之JWT認證
一、JWT認證
JWT構成
JWT分為三段式:頭、體、簽名(head、payload、sgin)
頭和體是可逆加密的,讓伺服器可以反解析出user對象,簽名是不可逆加密,保證整個token的安全性的。
頭、體、簽名三部分,都是採用JSON格式的字元串,進行加密,可逆加密一般蠶蛹base64演算法,不可逆加密一般採用hash(md5)演算法
- 頭中的內容是基本資訊:項目資訊等、
{
'company': '項目資訊',
...
}
- 體中的內容是關鍵資訊:用戶主鍵、用戶名、簽發時客戶端資訊(設備號,地址)、過期時間
{
'user_id': 2,
'username': 'xiaoyang',
...
}
- 簽名中的內容是安全資訊:頭的加密結果 + 體的加密結果 + 伺服器不對外公開的安全碼 進行md5加密
{
"head": "頭的加密字元串",
"payload": "體的加密字元串",
"secret_key": "安全碼"
}
過期時間配置:
import datetime
JWT_AUTH={
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 過期時間,手動配置
}
校驗過程
- 將token按
『 . 』
拆分為三段字元串 - 第一段:頭加密字元串,一般不需要做任何處理
- 第二段:體加密字元串,要反解出用戶主鍵,通過主鍵從User表中就能得到登錄用戶,過期時間和設備資訊都是安全資訊,確保token沒有過期,且是同一設備來的
- 在用 第一段 + 第二段 + 伺服器安全碼 進行md5加密,與第三段 」簽名字元串「 進行對比校驗,通過後才能代表第二段校驗得到的user對象是合法的登錄用戶
DRF項目的JWT認證開發流程
- 用帳號密碼訪問登錄介面,登錄介面邏輯中調用簽發token演算法,得到token,返回給客戶端,客戶端自己存到Cookies中
- 校驗token的演算法應該寫在認證類中(在認證類中調用),全局配置給認證組件,所有視圖類請求,都會進行認證校驗,所以請求帶了token,就會反解析出user對象,在視圖類中用request.user就能訪問登錄的用戶。
第三方JWT認證
使用 JWT的第三方認證模組 django-rest-framework-jwk
安裝:pip install djangorestframework-jwk
簡單使用:
1、用戶密碼認證成功獲取token
# 首先創建超級用戶 》》python manage.py createsuperuser
from rest_framework_jwt.views import ObtainJSONWebToken, VerifyJSONWebToken, RefreshJSONWebToken
# 基類:JSONWebTokenAPIView 繼承了APIView
# ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken都繼承了JSONWebTokenAPIView
urlpatterns = [
path('login/', ObtainJSONWebToken.as_view()),
]
# 使用post請求帶著用戶和密碼去訪問//127.0.0.1:8000/login/,就會得到token
2、視圖訪問 JWT認證類
但是 第三方JWT的認證必須要在請求頭中添加 Authorization
和對應的 JWT +token參數
,才會進行認證,否則就不認證(不好用需要從新寫)
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class TestView(APIView):
# 局部配置
authentication_classes = [JSONWebTokenAuthentication]
def get(self, request):
return Response('認證測試')
# 全局配置需要在settings.py配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
]
}
自定義JWT認證類
由於第三方的 JWT認證必須要在請求中添加 Authorization欄位才會認證,否則不認證直接通過,所以不使用它,自己寫一個基於第三方JWT的認證類,這樣請求頭中沒有Authorization欄位,就會認證失敗。
# 由於使用的是基於第三方的認證,所有還是要繼承它,並且使用一些它的方法,而且還要重寫authenticate方法
# app_auth.py
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework_jwt.utils import jwt_decode_handler
from rest_framework import exceptions
class TestAuth(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
# 獲取JWT的token值
jwt_value = request.META.get('HTTP_AUTHORIZATION')
try:
# 認證
payload = jwt_decode_handler(jwt_value)
except Exception:
raise exceptions.AuthenticationFailed('認證失敗')
# 獲取用戶對象
user = self.authenticate_credentials(payload)
return user, None
# views.py
# 局部使用
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.app_auth import TestAuth
class TestView(APIView):
# 局部配置
authentication_classes = [TestAuth]
def get(self, request):
return Response('認證測試')
# 全局配置需要在settings.py配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'app01.app_auth.TestAuth',
]
}
二、手動簽發token(多方式登錄)
當用戶使用用戶名,手機號,郵箱等都可以登錄
如:前端需要傳的數據格式
{
"username":"xiaoyang/13313311333/[email protected]",
"password":"111111ys"
}
url
# post請求調用LoginView視圖中的login函數
re_path('login/', views.LoginView.as_view({'post': 'login'}))
views.py
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from app01.ser import UserModelSerializer
class LoginView(ViewSet):
def login(self, request):
# 序列化一個類
login_ser = UserModelSerializer(data=request.data, context={})
# 驗證,會調用序列化器中的鉤子函數
if login_ser.is_valid():
token = login_ser.context.get('token')
username = login_ser.context.get('username')
# 驗證成功返回用戶名和token
return Response({'username': username, 'token': token})
else:
# 驗證失敗返回錯誤資訊
return Response(login_ser.errors)
ser.py
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler
from app01.models import User
import re
class UserModelSerializer(serializers.ModelSerializer):
# 重新覆蓋username欄位,資料庫中它是唯一的(unique),post會認為是你自己保存數據,自己校驗沒過
username = serializers.CharField(max_length=16)
class Meta:
model = User
fields = ['username', 'password']
# 全局鉤子
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
if re.match(r'^1[3-9]\d{9}', username):
# 電話登錄
user = User.objects.filter(phone=username).first()
elif re.match(r'.*@.*', username):
# 郵箱登錄
user = User.objects.filter(email=username).first()
else:
# 用戶名登錄
user = User.objects.filter(username=username).first()
if user:
# 校驗密碼,因為用的是auth模組,所以使用check_password
user.check_password(password)
payload = jwt_payload_handler(user) # 放入用戶生成payload
token = jwt_encode_handler(payload) # 放入payload生成token
self.context['token'] = token
self.context['username'] = user.username
return attrs
else:
raise ValidationError('用戶名或密碼錯誤')