DRF之JWT介绍与使用

JWT介绍和原理

JWT即Json、Web、Token,只针对于web方向的token方式验证。在我们传统登录认证中,都是使用session来认证,而JWT是使用token来认证。

使用session认证时,session都是保存在服务器中,在进行认证过程中,都会去数据库中进行校验(IO操作),效率低。

而token,它保存在客户端中,与cookie一样,也是服务器生成后返回给客户端的,token的格式是三个字符串拼到一起的:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

第一段我们称它为头部(header);第二段我们称其为载荷(payload,一般都是用户id、用户名、token生效时间等);第三段是签证(signature),由第一段和第二段通过某种加密方式+秘钥加密得到的。

在认证过程中,服务器会先获取第一段和第二段的字符串,通过某种加密方式+秘钥加密得到的新字符串和第三段进行比较,如果一样,那就是认证成功。

token的第一段和第二段字符串其实是通过base64编码得到的,并不是加密后的字符串。

base64解码与解码:

import json
import base64

d = {'typ': 'JWT', 'alg': 'HS256'}
s = json.dumps(d)

# 把字符串进行b64编码
res = base64.b64encode(bytes(s, encoding='utf-8'))
print(res)

# 把b64编码的字符串,解码
b = b'eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJIUzI1NiJ9'
res = base64.b64decode(b)
print(res)

使用JWT

JWT签发(生成token)

安装模块:

pip install djangorestframework-jwt

创建auth的user表用户。

createsuperuser

路由配置:

from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path('login/', obtain_jwt_token),
]

向login/路由发送post请求,进行登录。

image

修改登录成功返回格式

登录成功后只会返回token数据,这样子可能不太好看,所以我们可以自定义返回提示信息。

首先写一个函数:

def my_jwt_response(token, user=None, request=None):
    return {'code': 100, 'msg': '登陆成功', 'token': token, 'username': user.username}

配置文件中添加:

JWT_AUTH = {
    # 过期时间7天
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.my_jwt_response',  # 函数路径
}

再次登录:

image

JWT认证

视图类:

from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication

class BookView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # jwt认证类
    authentication_classes = [JSONWebTokenAuthentication, ]
    # jwt认证类有个小bug,需要配合IsAuthenticated类
    permission_classes = [IsAuthenticated, ]

    def get(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

访问books/路由,需要在请求头中添加token。

  • 键:authorization
  • 值:jwt + 空格 + token字符串

image

JWT认证不通过时有各种提示信息:比如

token过期

image

解码错误

image

自定义JWT

签发

用户表:

class User(models.Model):
    username = models.CharField(max_length=16)
    password = models.CharField(max_length=16)

登录视图:

from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

class LoginView(APIView):
    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user_obj = User.objects.filter(username=username, password=password).first()
        if user_obj:
            """登录成功,进行签发"""
            # 根据登录用户获取载荷
            payload = jwt_payload_handler(user_obj)
            print(payload)
            # 根据载荷获取token
            token = jwt_encode_handler(payload)
            return Response({'code': 100, 'msg': '登录成功', 'token': token})
        return Response({'code': 99, 'msg': '用户名或密码错误'})

路由配置:

urlpatterns = [
    path('login/', views.LoginView.as_view()),
]

image

认证

编写认证类:

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.settings import api_settings

jwt_decode_handler = api_settings.JWT_DECODE_HANDLER

class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 从请求头获取token
        jwt_value = request.META.get('HTTP_TOKEN')
        try:
            # 根据token获取载荷
            payload = jwt_decode_handler(jwt_value)
        except Exception:
            raise AuthenticationFailed('你还未登录')
        # 根据载荷中存的用户id获取用户对象
        user = User.objects.filter(pk=payload['user_id'])
        return user, jwt_value

视图类:

class BookView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    # 添加自己编辑的认证类
    authentication_classes = [LoginAuth, ]
    def get(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

token错误、过期,或未携带,都提示未登录

image

携带正确的token

image

posted @ 2022-06-23 20:33  Yume_Minami  阅读(94)  评论(0编辑  收藏  举报