JWT认证
drf-jwt
drf-jwt:官网http://getblimp.github.io/django-rest-framework-jwt/
安装子:虚拟环境
pip install djangorestframework-jwt
jwt模块
模块包:rest_framework_jwt
才有drf-jwt框架,后期任务只需要书写登录
为什么要重写登录:drf-jwt只完成了账号密码登录,我们还需要手机登录,邮箱登录
为什么不需要重写认证类:因为认证规则已经完成且固定不变,变得只有认证字符串的前缀,前缀可以在配置文件中配置
工作原理
"""
1) jwt = base64(头部).base(载荷).hash256(base64(头部).base(载荷).密钥)
2) base64是可逆的算法、hash256是不可逆的算法
3) 密钥是固定的字符串,保存在服务器
"""
jwt认证规则
"""
全称:json web token
解释:加密字符串的原始数据是json,后台产生,通过web传输给前台存储
格式:三段式 - 头.载荷.签名 - 头和载荷才有的是base64可逆加密,签名才有md5不可逆加密
内容:
头(基础信息,也可以为空):加密方式、公司信息、项目组信息、...
载荷(核心信息):用户信息、过期时间、...
签名(安全保障):头加密结果+载荷加密结果+服务器秘钥 的md5加密结果
服务器签发(login)->web传送给前端存储 ->请求需要的登陆的结果在携带给后台 ->服务器校验认证组件=>权限管理
认证规则:
后台一定要保障 服务器秘钥 的安全性(它是jwt的唯一安全保障)
后台签发token -> 前台存储 -> 发送需要认证的请求带着token -> 后台校验得到合法的用户
"""
为什么要才有jwt认证(优点)
- 后台不需要存储token,只需要存储签发与校验token的算法,效率远远大于后台存储和取出token完成校验
- jwt算法认证,更适合服务器集群部署
服务器压力小,集群部署更加完善。
drf-jwt开发
使用:user/urls.py
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()),
配置信息:JWT_AUTH到settings.py中
# 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', # 前缀
}
序列化user:user/serializers.py(自己创建)
# 只参与序列化
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')
自定义response:user/utils.py
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)
基于drf-jwt的全局认证:user/authentications.py(自己创建)
# 自定义认证类
"""
认证模块工作原理
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)
"""
全局启用:settings.py
# 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', # 前缀
}
局部启用禁用:任何一个cbv类首行
# 局部禁用
authentication_classes = []
# 局部启用
from user.authentications import JSONWebTokenAuthentication
authentication_classes = [JSONWebTokenAuthentication]
多方式登录:user/utils.py
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
配置多方式登录:settings/dev.py
AUTHENTICATION_BACKENDS = ['user.utils.JWTModelBackend']
手动签发JWT:了解 - 可以拥有原生登录基于Model类user对象签发JWT
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)