DRF的JWT用戶認證
從根本上來說,JWT是一種開放的標准(RFC 7519), 全稱為json web token ,其存在的意義在於,對於前后端分離的項目來說,后端不需要存儲token,主需要存儲簽發和校驗token的算法,所以我們需要在前端存儲token,然后通過JWT加密之后傳送給后端,從而完成校驗.
JWT算法認證的最大優點在於適合服務器集群部署,這樣對於比較大的項目可以從根本上提升並發量.
JWT的認證規則
JWT的格式
jwt的格式是三段式,即header(頭) . payload(負載) . Signature(簽名)
,其中頭和載負載采用的是base64可逆的加密,簽名采用的則是md5不可逆的加密,具體如下:
- 頭(基礎信息): 包含兩部分,token類型以及采用的加密算法
- 載負載(核心信息): 主要是用戶信息,過期時間等
- 簽名(安全保證): 包括三方面,即頭加密結果+載荷加密結果+服務器秘鑰,采用的是md5加密
我們的后台一定要保證服務器秘鑰的安全,因為在jwt里面服務器秘鑰是唯一的安全保障
JWT認證的流程
后台簽發token -> 前台存儲 -> 前台想后台發送需要認證的請求且攜帶token -> 后台校驗token得到合法的用戶
JWT模塊的導入為
# 在cmd或者Terminal窗口中:
pip install djangorestframework-jwt
# 模塊包名為rest_framework_jwt
JWT的使用
JWT最常用的是三個接口,簽發token,校驗token以及刷新token,我們可以分別在urls.py里面導入
# /urls.py
from django.conf.urls import url
from . import views
from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_token, verify_jwt_token, refresh_jwt_token
urlpatterns = [
# drf-jwt三個視圖接口
url(r'^login/$', obtain_jwt_token),
url(r'^verify/$', verify_jwt_token),
url(r'^refresh/$', refresh_jwt_token),
]
# 導入之后,我們還需要在settings.py里面配置相關的JWT的配置項
# /settings.py
import datetime
JWT_AUTH = {
# 設置JWT的過期時間
'JWT_EXPIRATION_DELTA':datetime.timedelta(days=7),
# 自定義jwt插件的配置
'JWT_PEFRESH_EXPIRATION_DELTA':detetime.timedelta(days=7),
# 設置JWT的頭
'JWT_AUTH_HEADER_PREFIX':'JWT'
}
下面我們就用代碼來實現jwt的整個邏輯,大概邏輯如下:
- 視圖類中,將請求數據交給序列化完成校驗,然后返回用戶信息和對應token
- 序列化類,自定義反序列化的字段,利用全局鈎子校驗數據得到user和token,並且保存在序列化類對象中
- token可以采用jwt插件的rest_framework_jwt.serializers中jwt_payload_handler,jwt_encode_handler來完成簽發
下面我們做一個實例,該小例子實現了用戶的多方式登錄:
# urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^login/$', views.LoginAPIView.as_view())
]
# serializers.py
from rest_framework.serializers import ModelSerializer, CharField, ValidationError, SerializerMethodField
from . import models
import re
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
class LoginSerializer(ModelSerializer):
# 定義用戶名和密碼兩個僅支持反序列化的字段
username = CharField(write_only=True)
password = CharField(write_only=True)
class Meta:
model = models.User
fields = ('username', 'password')
# 在全局鈎子中簽發token
def validate(self, attrs):
user = self._many_method_login(**attrs)
# 將數據存放到序列化對象中
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
self.user = user
self.token = token
return attrs
# 多方式登錄
def _many_method_login(self, **attrs):
username = attrs.get('username')
password = attrs.get('password')
# 正則匹配,如果有@符號,則判定為郵箱登錄
if re.match(r'.*@.*', username):
user = models.User.objects.filter(email=username).first()
# 如果是11位數字且開頭為1,判定為手機登錄
elif re.match(r'^1[0-9]{10}$', username):
user = models.User.objects.filter(mobile=username).first()
# 兩個都不匹配的話,就判定為用戶名登錄
else:
user = models.User.objects.filter(username=username).first()
# 如果用戶不存在,就是信息有誤
if not user:
raise ValidationError({'username': '賬號有誤'})
# 如果用戶存在,但是檢測密碼有問題,報錯密碼有誤
if not user.check_password(password):
raise ValidationError({'password': '密碼有誤'})
return user
# views.py
from rest_framework.views import APIView
from . import models, serializers
from utils.response import APIResponse
class LoginAPIView(APIView):
authentication_classes = []
permission_classes = []
# 以post的方式接受前台發送的數據
def post(self, request, *args, **kwargs):
# 將前台傳來的數據傳送到序列化對象中,完成校驗
serializer = serializers.LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
return APIResponse(msg='login success', data={
'username': serializer.user.username,
'token': serializer.token
})