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)