drf-jwt认证

JWT认证

drf-jwt

drf-jwt:官网http://getblimp.github.io/django-rest-framework-jwt/前端

安装子:虚拟环境python

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认证(优势)

1) 后台不须要存储token,只须要存储签发与校验token的算法,效率远远大于后台存储和取出token完成校验
2) jwt算法认证,更适合服务器集群部署git

服务器压力小,集群部署更加完善。github

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)