drf JWT组件

文章目录

  • drf JWT组件
    • JWT 原理
    • 校验方式
    • drf项目多的jwt认证开发流程(重点)
    • JWT 简单使用
    • base64编码解码
    • JWT 控制登录接口返回的数据格式
    • 自定义基于jwt的权限类
    • 手动签发token(多方式登录实现)
      • 方式1 逻辑写在序列化类中
      • 方式2 逻辑写在视图类中
    • JWT 的配置参数

drf JWT组件

jwt = Json Web Token

JWT 原理

  1. jwt分三段式:头.体.签名 (head.payload.sgin)

  2. 头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的

  3. 头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法

  4. 头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息

    {
    	"company": "公司信息",
    	...
    }
    
  5. 体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间

    {
    	"user_id": 1,
    	...
    }
    
  6. 签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密

    {
    	"head": "头的加密字符串",
    	"payload": "体的加密字符串",
    	"secret_key": "安全码"
    }
    

校验方式

  1. 将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
  2. 第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
  3. 再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户

drf项目多的jwt认证开发流程(重点)

  1. 用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中
  2. 校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户

注:登录接口需要做 认证 + 权限 两个局部禁用

JWT 简单使用

第三方写好的 django-rest-framework-jwt

pip install djangorestframework-jwt

新建一个项目,继承AbstractUser表()

class User(AbstractUser):
    phone = models.CharField(max_length=11)
    avatar = models.ImageField(upload_to='avatar')  # ImageField依赖于pillow模块
    
补充:
    # 配置头像相关
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'meida')

    # 重写User表相关
    AUTH_USER_MODEL = 'api.user'

rest_framework_jwt.views源码

from rest_framework_jwt.views import ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken
    基类, JSONWebTokenAPIView 继承了APIView
    ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken都继承JSONWebTokenAPIView
    rest_framework_jwt.views源码: 
        obtain_jwt_token = ObtainJSONWebToken.as_view()
        refresh_jwt_token = RefreshJSONWebToken.as_view()
        verify_jwt_token = VerifyJSONWebToken.as_view()

url配置

urlpatterns = [
    path('login/', ObtainJSONWebToken.as_view()),  # 方式1
    path('login/', obtain_jwt_token)   # 方式2
]

views.py

from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated


class BookView(APIView):
    # 必须加上这个权限认证,否则用户不登录就能访问,那么JWT认证就没意义了
    permission_classes = [IsAuthenticated]  
    authentication_classes = [JSONWebTokenAuthentication]

    def get(self, request):
        return Response("ok")

效果:
drf JWT组件_第1张图片

drf JWT组件_第2张图片

自定制Token认证

  • 定制后,不携带token参数,不允许访问指定视图
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework_jwt.authentication import jwt_decode_handler
from rest_framework import exceptions

class MyToken(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        jwt_value = str(request.META.get('HTTP_AUTHORIZATION'))
        # 认证
        try:
            payload = jwt_decode_handler(jwt_value)

        except Exception:
            raise exceptions.AuthenticationFailed("认证失败")
        user = self.authenticate_credentials(payload)
        return user, None
    
views.py
class BookView(APIView):
    authentication_classes = [MyToken]

    def get(self, request):
        return Response("ok")

drf JWT组件_第3张图片

base64编码解码

base64是python内置的模块,也是JWT认证中所用编码和解码方式,需要注意的是,他不算是加密方式,但是对于小白来说也算是加密了,与md5不同的是,md5是固定长度,不可反解,base64是可变长,可反解的。

import base64
import json

# 编码,处理json格式字符串
dic = {"name":"xxx", "age":18}
dic_str = json.dumps(dic) 
ret = base64.b64encode(dic_str.encode('utf8'))  # 只能编码二进制格式数据

# 解码
dic1 = base64.b64decode(ret)

JWT 控制登录接口返回的数据格式

	-第一种方案,自己写登录接口
    -第二种写法,用内置,控制登录接口返回的数据格式
    	-jwt的配置信息中有这个属性
    	    'JWT_RESPONSE_PAYLOAD_HANDLER':
    'rest_framework_jwt.utils.jwt_response_payload_handler',
    	-重写jwt_response_payload_handler,配置成咱们自己的
     源码:
        def jwt_response_payload_handler(token, user=None, request=None):
            return {
                'token': token
            }
     重写:
        def re_jwt_response_payload_handler(token, user=None, request=None):
            return {
                'status': 100,
                'msg': '登录成功',
                'username': user.username,
                'token': token,
            }
     settings.py中添加配置:
        JWT_AUTH = {
            'JWT_RESPONSE_PAYLOAD_HANDLER':
            'auth.re_jwt_response_payload_handler',  # 自己写的路径
        }

        

在这里插入图片描述

自定义基于jwt的权限类

from rest_framework.authentication import BaseAuthentication  # 基于它
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication # 基于它
from rest_framework.exceptions import AuthenticationFailed
# from rest_framework_jwt.authentication import jwt_decode_handler
from rest_framework_jwt.utils import jwt_decode_handler # 跟上面是一个
import jwt

from api import models

class MyJwtAuthentication(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        jwt_value=request.META.get('HTTP_AUTHORIZATION')
        if jwt_value:
            try:
            #jwt提供了通过三段token,取出payload的方法,并且有校验功能
                payload=jwt_decode_handler(jwt_value)
            except jwt.ExpiredSignature:
                raise AuthenticationFailed('签名过期')
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('用户非法')
            except Exception as e:
                # 所有异常都会走到这
                raise AuthenticationFailed(str(e))
            # 第一种,去数据库查
		   # user=models.User.objects.get(pk=payload.get('user_id'))
		   # 第二种不查库,不过生成的对象只有id和username
            # user=models.User(id=payload.get('user_id'),username=payload.get('username'))
            # 第三种内置方法,原理就是去数据库中查数据
            user=self.authenticate_credentials(payload)
            return user,jwt_value
        # 没有值,直接抛异常
        raise AuthenticationFailed('您没有携带认证信息')
# 使用全局局部配置都可

drf JWT组件_第4张图片

手动签发token(多方式登录实现)

  • 使用用户名,手机号,邮箱,都可以登录

前端数据格式

{
"username":"yxh/1332323223/[email protected]",
"password":"yxh123"
}

方式1 逻辑写在序列化类中

views.py

class LoginView(ViewSet):

    def login(self, request, *args, **kwargs):
        login_ser = ser.LoginModelSerializer(data=request.data, context={'request': request})
        login_ser.is_valid(raise_exception=True)
        token = login_ser.context.get('token')
        username = login_ser.context.get('username')
        return Response({'status': 100, 'msg': '登录成功', 'username': username, 'token': token})  

ser.py

class LoginModelSerializer(serializers.ModelSerializer):
    # 重新覆盖username字段,数据中它是unique,post,认为你保存数据,自己有校验没过
    username = serializers.CharField()

    class Meta:
        model = models.User
        fields = ['username', 'password']

    def validate(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        # 通过username的数据不同判断,查询字段
        if re.match("^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$", username):
            user = models.User.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):  # 邮箱
            user = models.User.objects.filter(email=username).first()
        else:
            user = models.User.objects.filter(username=username).first()
        # 校验用户是否存在
        if not user:
            raise ValidationError('用户不存在')
        if not user.check_password(password):
            raise ValidationError('密码错误')
        payload = jwt_payload_handler(user)  # 把user传入,得到payload
        token = jwt_encode_handler(payload)  # 把payload传入,得到token
        self.context['token'] = token
        self.context['username'] = user.username
        return attrs

方式2 逻辑写在视图类中

views.py

class LoginView2(ViewSet):

    def login(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        # print(username, password)
        # 通过username的数据不同判断,查询字段
        if re.match("^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$", username):
            user = models.User.objects.filter(phone=username).first()
        elif re.match('^.+@.+$', username):  # 邮箱
            user = models.User.objects.filter(email=username).first()
        else:
            user = models.User.objects.filter(username=username).first()
        # 校验用户是否存在
        if not user:
            raise ValidationError('用户不存在')
        if not user.check_password(password):
            raise ValidationError('密码错误')
        payload = jwt_payload_handler(user)  # 把user传入,得到payload
        token = jwt_encode_handler(payload)  # 把payload传入,得到token
        username = user.username

        login_ser = ser.LoginModelSerializer2(data=request.data)
        login_ser.is_valid(raise_exception=True)


        return Response({'status': 100, 'msg': '登录成功', 'username': username, 'token': token})

ser.py

class LoginModelSerializer2(serializers.ModelSerializer):
    # 重新覆盖username字段,数据中它是unique,post,认为你保存数据,自己有校验没过
    username = serializers.CharField()

    class Meta:
        model = models.User
        fields = ['username', 'password']

JWT 的配置参数

# jwt的配置
import datetime
JWT_AUTH={
    'JWT_RESPONSE_PAYLOAD_HANDLER':'app02.utils.my_jwt_response_payload_handler',
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 过期时间,手动配置
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7), # token验证刷新时间,默认就是7天
}

你可能感兴趣的:(#,Django框架,python,django,drf,安全)