DRF JWT认证基础

JWT认证

【1】base64使用

(1)使用场景

  1. 电子邮件附件:由于电子邮件协议只支持 ASCII 字符集,因此,如果要发送非 ASCII 数据(如图片、音频、视频等),需要先将这些数据进行 base64 编码,然后再作为邮件内容发送。
  2. 网页中的嵌入资源:有时为了简化网页资源加载(如图片、CSS、JavaScript 等),可以将其内容使用 base64 编码,然后直接嵌入到 HTML 代码中,从而避免额外的 HTTP 请求。
  3. 配置信息:在某些情况下,配置信息(如 API 密钥、密码等)需要以明文形式存储在配置文件中,但又不想直接暴露明文。此时,可以使用 base64 进行编码。

(2)方法介绍

  • 编码base64.b64encode(s)
    • s:要编码的字节串(bytes)。
      • 类型需要是bytesstr不行
    • 返回:编码后的字节串
  • 解码base64.b64decode(s)
    • s:要解码的 base64 编码的字节串。
      • 长度如果不是4的倍数,后面补等号
    • 返回:解码后的原始字节串

(3)使用示例

  • jsonbase64搭配使用
import base64
import json

info_dict = {'name': "bruce", "age": 18}
info_str = json.dumps(info_dict).encode('utf8')
info_bytes = info_str

# 编码
base_data_en = base64.b64encode(info_bytes)
print(base_data_en)  # b'eyJuYW1lIjogImJydWNlIiwgImFnZSI6IDE4fQ=='

# 解码
base_data_de = base64.b64decode(base_data_en)
print(base_data_de)  # b'{"name": "bruce", "age": 18}'
  • 图片和base64
import base64

# 原始数据URI字符串
picture_data_base64 = ''
# 分割字符串以获取base64编码部分
base64_encoded_data = picture_data_base64.split(',')[-1]

# 解码base64数据
picture_bytes = base64.b64decode(base64_encoded_data)

# 将解码后的数据写入文件
with open('1.png', 'wb') as f:
    f.write(picture_bytes)

【2】JWT原理

(1)回顾cookie和session

  • 补充:浏览器根据域名是否相同判断发送指定的cookie信息

    • 浏览器在根据域名判断是否发送cookie时,遵循的是同源策略(Same-origin policy)。
    • 同源策略是浏览器的一种安全策略,它限制了从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。
    • 这里的“源”指的是协议、域名和端口三者的组合
  • cookie

    • 首先浏览器携带用户信息发送给后端,后端进行数据判断以后,执行方法set_cookie,构造一个字典发送给浏览器
    • 浏览器将cookie保存在浏览器中,并且还是明文保存
    • 在之后需要登录认证的操作中,携带cookie进行验证登录

image-20240418185619745

  • session
    • 这里只说不同点,保存的数据是加密的,键值是随机字符串,数据保存在数据库中,信息都是密文

image-20240418191545579

(2)JWT原理

image-20240419144838690

  • JWT(JSON Web Token)的原理基于一种身份认证方案,它使用密钥对数据进行加密和解密,根据两次数据是否一致,判断信息有没有被串改,是签发的数据就可以登录。

    • 加密过程将原始数据(即载荷)和密钥一起进行哈希运算,生成加密后的数据。
    • 解密过程则使用相同的密钥和加密后的数据进行哈希运算,还原出原始数据。
  • JWT主要由三部分构成:头部(Header)、载荷(Payload)和签名(Signature),由.符号分隔。

    1. Header:第一部分是声明类型,例如JWT;第二部分是声明所使用的算法,例如HMAC SHA256RSA等。
    2. Payload:它包含了具体的用户信息,如用户ID、用户名、角色等,也可以包含自定义的其他信息。
    3. Signature:它使用Header和Payload中的数据以及一个密钥来生成签名。签名的作用是确保数据在传输过程中没有被篡改,并且可以由接收方验证发送方的身份。
  • 特点:

    • 数据没有保存在数据库中,对大型项目很有用

【3】simple-jwt简单使用

(1)安装

pip install djangorestframework-simplejwt

(2)使用

  • 注册路由:

    • 首先导包from rest_framework_simplejwt.views import token_obtain_pair

    • 然后编写路由:路由可以自定义,视图函数使用token_obtain_pair

    • from rest_framework_simplejwt.views import token_obtain_pair
      from django.urls import path
      urlpatterns = [
          path('user/', token_obtain_pair),
      ]
      
  • 指定需要登录的视图函数添加需要认证方法

    • 导包from rest_framework_simplejwt.authentication import JWTAuthentication
    • 导包from rest_framework.permissions import IsAuthenticated
    • 添加到视图中
      • permission_classes = [IsAuthenticated]
      • authentication_classes = [JWTAuthentication]
  • 注册一个用户(需要使用django的user表

    • 创建一个超级用户admin
  • 未登录测试

    • image-20240418195018884
  • 登录测试

    • 首先登录获取access

    • image-20240418195229380

    • headers携带信息进行验证

    • image-20240418195429284

(3)base64解码数据

  • 头部
    • 解码后的结果是{"alg":"HS256","typ":"JWT"},这是JWT的头部,指定了用于签名JWT的算法(在这个例子中是HS256)以及令牌的类型(JWT)。
  • 载荷
    • 这个载荷部分包含了关于这个JWT的一些声明信息,比如token_type表示这是一个访问令牌(access token),exp是令牌的过期时间(UNIX时间戳),iat是令牌的签发时间,jti是令牌的唯一标识符,而user_id是用户ID。
  • 签名:这不是能直接看懂的信息
import base64

info_str = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzEzNDQxMjE2LCJpYXQiOjE3MTM0NDA5MTYsImp0aSI6IjU3Y2YxMmNiNjA0OTQxNTU4ODY3ZDAzNGMyZmJmYzQ4IiwidXNlcl9pZCI6Mn0.aT07kjJtvQfRLvt8O6VPTvqIPChc35CLt_aLUUXZYPY'
info_one, info_two, info_three = info_str.split('.')
print(base64.b64decode(info_one.encode('utf8')))
# b'{"alg":"HS256","typ":"JWT"}'
print(base64.b64decode((info_two + '===').encode('utf8')))
# b'{"token_type":"access","exp":1713441216,"iat":1713440916,"jti":"57cf12cb604941558867d034c2fbfc48","user_id":2}'
print(base64.b64decode((info_three + '===').encode('utf8')))
# b'i=;\x922m\xbd\x07\xd1.\xfb|;\xa5ON\xfa\x88<(\\\xdf\x90\x8b\xb5\xa2\xd4QvX='

【4】练习:自定义jwt

(1)基础准备

  • 模型表:自定义一个普通的user
from django.db import models
class User(models.Model):
    username = models.CharField(max_length=64, verbose_name='用户名')
    password = models.CharField(max_length=64, verbose_name='密码')
  • 路由层
    • user路由的视图类后续编写,books路由用于检验自定义jwt
from django.urls import path, include
from .views import BookAPIView
from rest_framework.routers import SimpleRouter
from .views import LoginJWT


router.register(prefix='books', viewset=BookAPIView, basename='books')
urlpatterns = [
    path('', include(router.urls)),
    path('user/', LoginJWT.as_view()),
]

  • 书籍相关(检测自定义jwt)
    • JwtAuth是自定义的jwt认证类
# 序列化
from rest_framework.serializers import ModelSerializer
from .models import Book
class BookModelSerializer(ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'
# 视图层
from rest_framework.viewsets import ReadOnlyModelViewSet
from .serializer import BookModelSerializer
from .models import Book
from .authentication import JwtAuth
class BookAPIView(ReadOnlyModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    pagination_class = CommonPagination
    authentication_classes = [JwtAuth]

(2)登录验证接口

  • 仿照jwt的格式返回格式
  • 头部header:{'alg': "MD5", 'typ': "JWT"}
  • 载荷payload:{"pk": obj.pk, "username": obj.username}
  • 签名signature:将头部和载荷和django的密钥进行组合,使用MD5加密
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import User
import base64
import json
import hashlib


class LoginJWT(APIView):
    headers = {'alg': "MD5", 'typ': "JWT"}

    def post(self, request):
        # 获取数据
        username = request.data.get("username")
        password = request.data.get("password")
        # 检验数据
        if not all([username, password]):
            return Response({"code": 1001, "msg": '信息不全,请补全信息。'})
        obj = User.objects.filter(username=username, password=password).first()
        if not obj:
            return Response({"code": 1002, "msg": "登录信息校验失败。"})
        # 编码数据
        header = base64.b64encode(json.dumps(self.headers).encode('utf8'))
        payload = base64.b64encode(json.dumps({"pk": obj.pk, "username": obj.username}).encode('utf8'))
        md5 = hashlib.md5(header + payload + settings.SECRET_KEY.encode('utf8'))
        signature = base64.b64encode(md5.hexdigest().encode('utf8'))
        # 三个数据都是bytes类型,需要转换成字符串才能拼接
        jwt = '.'.join([i.decode('utf8') for i in [header, payload, signature]])
        return Response({"code": 1002, "msg": "登录成功", 'jwt': jwt})

(3)登录认证类

  • 需要注意的是字符串str和字节bytes的转换,不同的方法需要的不同
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import User
from django.conf import settings
import json
import hashlib
import base64

class JwtAuth(BaseAuthentication):
    def authenticate(self, request):
        # 获取jwt
        jwt = request.META.get("HTTP_JWT")
        if not jwt:
            raise AuthenticationFailed("请先登录再重试。")
        try:
            # 无法拆分成三个会报错,直接直接异常
            header, payload, signature = jwt.split('.')
            # 将头部、载荷和django的密钥进行整合后在加密
            md5 = hashlib.md5((header + payload + settings.SECRET_KEY).encode('utf8'))
            signature_ok = base64.b64encode(md5.hexdigest().encode('utf8')).decode('utf8')
            # 判断是否被串改数据
            if signature_ok == signature:
                user_dict = json.loads(base64.b64decode(payload))
                obj = User.objects.filter(**user_dict)
                return obj, jwt
            raise AuthenticationFailed("jwt信息验证失败。")
        except Exception as e:
            raise AuthenticationFailed("jwt信息有误。")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值