Go 語言實現的中國行政區劃程式碼包 gbt2260
阿新 • • 發佈:2021-07-05
一、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請求帶著使用者和密碼去訪問http://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('使用者名稱或密碼錯誤')
學習之旅