drf-jwt:官網http://getblimp.github.io/django-rest-framework-jwt/前端
安裝子:虛擬環境python
pip install djangorestframework-jwt
模塊包:rest_framework_jwt 纔有drf-jwt框架,後期任務只須要書寫登陸 爲何要重寫登陸:drf-jwt只完成了帳號密碼登陸,咱們還須要手機登陸,郵箱登陸 爲何不須要重寫認證類:由於認證規則已經完成且固定不變,變得只有認證字符串的前綴,前綴能夠在配置文件中配置
""" 1) jwt = base64(頭部).base(載荷).hash256(base64(頭部).base(載荷).密鑰) 2) base64是可逆的算法、hash256是不可逆的算法 3) 密鑰是固定的字符串,保存在服務器 """
""" 全稱:json web token 解釋:加密字符串的原始數據是json,後臺產生,經過web傳輸給前臺存儲 格式:三段式 - 頭.載荷.簽名 - 頭和載荷纔有的是base64可逆加密,簽名纔有md5不可逆加密 內容: 頭(基礎信息,也能夠爲空):加密方式、公司信息、項目組信息、... 載荷(核心信息):用戶信息、過時時間、... 簽名(安全保障):頭加密結果+載荷加密結果+服務器祕鑰 的md5加密結果 服務器簽發(login)->web傳送給前端存儲 ->請求須要的登錄的結果在攜帶給後臺 ->服務器校驗認證組件=>權限管理 認證規則: 後臺必定要保障 服務器祕鑰 的安全性(它是jwt的惟一安全保障) 後臺簽發token -> 前臺存儲 -> 發送須要認證的請求帶着token -> 後臺校驗獲得合法的用戶 """
1) 後臺不須要存儲token,只須要存儲簽發與校驗token的算法,效率遠遠大於後臺存儲和取出token完成校驗
2) jwt算法認證,更適合服務器集羣部署git
服務器壓力小,集羣部署更加完善。github
from django.conf.urls import url from . import views from rest_framework_jwt.views import refresh_jwt_token from rest_framework_jwt.views import obtain_jwt_token from rest_framework_jwt.views import verify_jwt_token from rest_framework_jwt.views import ObtainJSONWebToken url(r'^obtain/$', obtain_jwt_token), # 獲取token url(r'^verify/$', verify_jwt_token), # 驗證token是否正確,正確原樣返回 url(r'^refresh/$', refresh_jwt_token), # 提供一個token,刷新一個token,時間日後推遲 # url(r'^login/$', ObtainJSONWebToken.as_view()) # 實時刷新token url(r'^login/$', views.LoginAPIView.as_view()),
# drf-jwt配置 import datetime JWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 過時時間 # 'JWT_ALLOW_REFRESH': True, # 對refresh_jwt_token 容許刷新 # 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7), # 刷新的過時時間 'JWT_AUTH_HEADER_PREFIX': 'JWT', # 前綴 }
# 只參與序列化 class UserModelSerializer(ModelSerializer): # 改了原數據庫字段的序列化方式 password = SerializerMethodField() def get_password(self, obj): return '########' class Meta: model = models.User fields = ('username', 'password', 'mobile', 'email', 'first_name', 'last_name')
from rest_framework.response import Response class APIResponse(Response): # 格式化data def __init__(self, status=0, msg='ok', results=None, http_status=None, headers=None, exception=False, **kwargs): data = { # json的response基礎有數據狀態碼和數據狀態信息 'status': status, 'msg': msg } if results is not None: # 後臺有數據,響應數據 data['results'] = results data.update(**kwargs) # 後臺的一切自定義響應數據直接放到響應數據data中 super().__init__(data=data, status=http_status, headers=headers, exception=exception)
# 自定義認證類 """ 認證模塊工做原理 1)繼承BaseAuthentication類,重寫authenticate方法 2)認證規則(authenticate方法實現體): 沒有攜帶認證信息,直接返回None => 遊客 有認證信息,校驗失敗,拋異常 => 非法用戶 有認證信息,校驗出User對象 => 合法用戶 """ from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed class TokenAuthentication(BaseAuthentication): prefix = 'Token' def authenticate(self, request): # 拿到前臺的token auth = request.META.get('HTTP_AUTHORIZATION') # 沒有返回None,有進行校驗 if not auth: return None auth_list = auth.split() if not (len(auth_list) == 2 and auth_list[0].lower() == self.prefix.lower()): raise AuthenticationFailed('非法用戶') token = auth_list[1] # 校驗算法 user = _get_obj(token) # 校驗失敗拋異常,成功返回(user, token) return (user, token) # 校驗算法(認證類)與簽發算法配套 """ 拆封token:一段 二段 三段 用戶名:b64decode(一段) 用戶主鍵:b64decode(二段) 碰撞解密:md5(用戶名+用戶主鍵+服務器祕鑰) == 三段 """ import base64, json, hashlib from django.conf import settings from api.models import User def _get_obj(token): token_list = token.split('.') if len(token_list) != 3: raise AuthenticationFailed('token異常') username = json.loads(base64.b64decode(token_list[0])).get('username') pk = json.loads(base64.b64decode(token_list[1])).get('pk') md5_dic = { 'username': username, 'pk': pk, 'key': settings.SECRET_KEY } if token_list[2] != hashlib.md5(json.dumps(md5_dic).encode()).hexdigest(): raise AuthenticationFailed('token內容異常') user_obj = User.objects.get(pk=pk, username=username) return user_obj """ 認證類的認證核心規則 def authenticate(self, request): token = get_token(request) try: user = get_user(token) # 校驗算法 except: raise AuthenticationFailed() return (user, token) """
# drf-jwt配置 import datetime JWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 過時時間 # 'JWT_ALLOW_REFRESH': True, # 對refresh_jwt_token 容許刷新 # 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7), # 刷新的過時時間 'JWT_AUTH_HEADER_PREFIX': 'JWT', # 前綴 }
# 局部禁用 authentication_classes = [] # 局部啓用 from user.authentications import JSONWebTokenAuthentication authentication_classes = [JSONWebTokenAuthentication]
from rest_framework.serializers import ModelSerializer, CharField, ValidationError, SerializerMethodField from . import models from django.contrib.auth import authenticate 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 = authenticate(**attrs) # 帳號密碼登陸 => 多方式登陸 user = self._many_method_login(**attrs) # 簽發token,並將user和token存放到序列化對象中 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() # type: models.User elif re.match(r'^1[3-9][0-9]{9}$', 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
AUTHENTICATION_BACKENDS = ['user.utils.JWTModelBackend']
from rest_framework_jwt.settings import api_settings jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload)