JWT认证

在用户注册信息登陆后,想记录用户的登录状态,或者为用户创建身份认证的凭证,不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制

1、构成

JWT就是一段字符串,由三段信息文本用.链接一起就构成了JWT字符串,

aaaa.bbbb.cccc

第一个部分成为头部(header),第二部分成为载荷(payload),第三部分是签名(signature)

2、原理

  1. jwt分三段式:头.体.签名 (head.payload.sgin)
  2. 头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性
  3. 头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
  4. 头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
  "company": "公司信息",
  ...
}
  1. 体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
  "user_id": 1,
  "username":"zhangsan",
  ...
}
  1. 签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
  "head": "头的加密字符串",
  "payload": "体的加密字符串",
  "secret_key": "安全码"
}

3、校验

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

3、drf项目的jwt认证开发流程

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

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

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

4、drf简单使用jwt

安装
 pip install djangorestframework-jwt 
继承AbstractUser表
#新建项目,继承AbstractUser表,做数据库迁移,配置文件中配置AUTH_USER_MODEL = 'app01.User'   # '应用名.表名'

from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
    phone = models.CharField(max_length=32)
    icon = models.ImageField(upload_to='icon')  # ImageField依赖于pillow模块
创建超级用户
python manage.py  createsuperuser 
简单使用
from django.contrib import admin
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token,ObtainJSONWebToken, VerifyJSONWebToken, RefreshJSONWebToken

# 基类:JSONWebTokenAPIView,继承了APIView
# ObtainJSONWebToken, VerifyJSONWebToken, RefreshJSONWebToken都继承了JSONWebTokenAPIView
"""
obtain_jwt_token = ObtainJSONWebToken.as_view()
refresh_jwt_token = RefreshJSONWebToken.as_view()
verify_jwt_token = VerifyJSONWebToken.as_view()
"""

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/',obtain_jwt_token)#用下面那个也可以
    # path('login/', ObtainJSONWebToken.as_view())
]

发送get请求会报错

image-20221211134557687

必须发post请求

image-20221211134954891

简单认证使用
#views.py

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

class TestView(APIView):
    authentication_classes = [JSONWebTokenAuthentication]
    def get(self, request):
        return Response('ok')
    
    
#urls.py
path('test/', views.TestView.as_view()),


#settings.py中要注册
INSTALLED_APPS = [
    'rest_framework',
    'rest_framework_jwt'
]

image-20221211141310776

过期时间到了后会显示签名过期

image-20221211140933111

5、使用jwt自定制认证类

基于BaseJSONWebTokenAuthentication写的
- 取出token
- 调用jwt提供的解析出payload的方法,(校验是否过期,是否合法,如果合法,返回荷载信息
- 转成user对象
- 返回
#auth.py,自定义认证类

from rest_framework_jwt.authentication import jwt_decode_handler
from rest_framework import exceptions
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication


class MyToken(BaseJSONWebTokenAuthentication):
    def authenticate(self, request):
        jwt_value = str(request.META.get("HTTP_AUTHORIZATION"))  # 获取到token字符串
        # 认证
        try:
            payload = jwt_decode_handler(jwt_value)#把三段式解析出来,看认证是否篡改,是否过期
        except Exception:
            raise exceptions.AuthenticationFailed('认证失败')
        user = self.authenticate_credentials(payload)  # user是当前登录用户,
        return user, payload
#views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from app01.auth import MyToken

class TestView(APIView):
    authentication_classes = [MyToken]

    def get(self, request):
        print(request.user)
        return Response('ok')
  
#urls.py
path('test/', views.TestView.as_view()),    
基于BaseAuthentication写的
from rest_framework.authentication import BaseAuthentication
from rest_framework_jwt.authentication import jwt_decode_handler
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.utils import jwt_decode_handler  # 上面按个那个用哪个都一样
import jwt
from api.models import User


class MyJwtAuthenticationnnnn(BaseAuthentication):
    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))
            # 因为payload就是用户信息 的字典,
            print(payload)  # {'user_id': 1, 'username': 'zhao', 'exp': 1670811589, 'email': '13644@qq.com'}
            # return payload, jwt_value
            """需要得到user对象,第一种方式,取数据库查"""
            # user = User.objects.filter(pk=payload.get('user_id'))
            """第二种方式"""
            user = User(id=payload.get('user_id'), username=payload.get('username'))
            return user, jwt_value

        # 没有值直接抛异常
        raise AuthenticationFailed('没有携带认证信息')
#urls.py
path('goods/',views.GoodsInfoAPIView.as_view()),

#views.py
from app02.utils import MyJwtAuthenticationnnnn


class GoodsInfoAPIView(APIView):
    authentication_classes = [MyJwtAuthenticationnnnn]

    def get(self, request, *args, **kwargs):
        print(request.user)
        return Response('商品信息')

image-20221212102326109

image-20221212102810487

6、jwt控制访问权限

控制用户登录后才能访问,和不登陆就能访问

#1.控制用户登录后才能访问,和不登陆就能访问

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

"""
可以通过认证类JSONWebTokenAuthentication和权限类IsAuthenticated,来控制用户登陆以后才能访问某些接口
如果用户不登陆就能访问,只需要把权限类IsAuthenticated去掉
"""

class OrderAPIView(APIView):   #登录才能访问
    authentication_classes = [JSONWebTokenAuthentication]
    # 权限控制
    permission_classes = [IsAuthenticated]
    def get(self, request, *args, **kwargs):
        return Response('这是订单信息')


class UserInfoAPIView(APIView):    #不登陆就可以访问
    authentication_classes = [JSONWebTokenAuthentication]
    def get(self, request, *args, **kwargs):
        return Response('UserInfoAPIView')
    
    
#urls.py
#子路由
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
  path('login/',obtain_jwt_token),
  path('order/',views.OrderAPIView.as_view()),
  path('userinfo/',views.UserInfoAPIView.as_view())
]
#总路由
 path('app02/', include('app02.urls')),

登陆后才可访问

image-20221211203722091

不登陆就可访问

image-20221211203516209

7、jwt控制返回自定义数据格式

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

#2.控制登录接口返回的数据格式
 - 第一种方式,自己写登录接口
 - 第二种方式,用内置的,控制登录接口返回的数据格式
	-jwt的配置文件种有个这么个属性
    	  'JWT_RESPONSE_PAYLOAD_HANDLER': 'rest_framework_jwt.utils.jwt_response_payload_handler',
    -重写jwt_response_payload_handler,配置成自己的返回格式
#在app02下创建utils.py

def myjwt_response_payload_handler(token, user=None, request=None):#返回什么,前端就能看到什么

    return {
        'token': token,
        'msg':'登录成功',
        'status':100,
        'username':user.username
    }
#settings.py中配置自己重写的方法
JWT_AUTH={
    'JWT_RESPONSE_PAYLOAD_HANDLER':'app02.utils.myjwt_response_payload_handler'
}

再次登录

image-20221211205139618

8、多方式登录,自动签发token

#使用用户名,手机号,邮箱都可以登录
#前端需要传的数据格式
{
    "username":"zhao/13145786651/123@qq.com",
    "password":"123456"
}

模型类

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    mobile = models.CharField(max_length=32, unique=True)  # 唯一
    
#settings.py中配置
AUTH_USER_MODEL = 'api.User'    '应用.表名'

序列化类

from rest_framework import serializers
from api import models
import re
from rest_framework.exceptions import ValidationError
from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler


class LoginSerializer(serializers.ModelSerializer):
    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('^1[3-9][0-9]{9}$', username):  # 手机号
            user = models.User.objects.filter(models=username).first()
        elif re.match('.+@.+$', username):  # 邮箱
            user = models.User.objects.filter(email=username).first()
        else:#用户名
            user = models.User.objects.filter(username=username).first()
        if user:  # 用户存在
            # 校验密码
            if user.check_password(password):
                # 签发token
                payload = jwt_payload_handler(user)
                token = jwt_encode_handler(payload)
                # self.aa = token
                self.context['token'] = token
                self.context['username'] = user.username
                return attrs
            else:
                raise ValidationError('密码错误')
        else:
            raise ValidationError('用户不存在')

视图类

from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin, ViewSet
from app02 import serializer


# class LoginView(ViewSetMixin, APIView):
class LoginViewSet(ViewSet):  # 跟上面完全一样
    # def post(self, request):#不写post,直接写login
    #     #这是登录接口
    #     pass
    def login(self, request, *args, **kwargs):
        # 1.需要有一个序列化的类
        # 2.生成序列化类对象
        login_serializer = serializer.LoginSerializer(data=request.data,context={'request':request})
        # 3.调用序列化对象的is_validad
        login_serializer.is_valid(raise_exception=True)
        token = login_serializer.context.get('token')
        user = login_serializer.context.get('username')
        # 4. return
        return Response({'status': 100, 'msg': '登录成功', 'token': token,'username':user})

路由配置

path('login2/', views.LoginViewSet.as_view({'post': 'login'})),

image-20221212115825810

image-20221212115858977

9、配置过期时间

#settings.py种手动配置

import datetime
JWT_AUTH={
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),#过期时间手动配置7天

}
posted @ 2022-12-12 17:38  ExpiredSaury  阅读(32)  评论(0编辑  收藏  举报