JWT认证
【1】base64
使用
(1)使用场景
- 电子邮件附件:由于电子邮件协议只支持 ASCII 字符集,因此,如果要发送非 ASCII 数据(如图片、音频、视频等),需要先将这些数据进行
base64
编码,然后再作为邮件内容发送。 - 网页中的嵌入资源:有时为了简化网页资源加载(如图片、CSS、JavaScript 等),可以将其内容使用
base64
编码,然后直接嵌入到 HTML 代码中,从而避免额外的 HTTP 请求。 - 配置信息:在某些情况下,配置信息(如 API 密钥、密码等)需要以明文形式存储在配置文件中,但又不想直接暴露明文。此时,可以使用
base64
进行编码。
(2)方法介绍
- 编码
base64.b64encode(s)
s
:要编码的字节串(bytes
)。- 类型需要是
bytes
,str
不行
- 类型需要是
- 返回:编码后的字节串。
- 解码
base64.b64decode(s)
s
:要解码的base64
编码的字节串。- 长度如果不是4的倍数,后面补等号
- 返回:解码后的原始字节串。
(3)使用示例
json
和base64
搭配使用
import base64
import jsoninfo_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进行验证登录
- session
- 这里只说不同点,保存的数据是加密的,键值是随机字符串,数据保存在数据库中,信息都是密文
(2)JWT原理
-
JWT(JSON Web Token)的原理基于一种身份认证方案,它使用密钥对数据进行加密和解密,根据两次数据是否一致,判断信息有没有被串改,是签发的数据就可以登录。
- 加密过程将原始数据(即载荷)和密钥一起进行哈希运算,生成加密后的数据。
- 解密过程则使用相同的密钥和加密后的数据进行哈希运算,还原出原始数据。
-
JWT主要由三部分构成:头部(Header)、载荷(Payload)和签名(Signature),由
.符号
分隔。Header
:第一部分是声明类型,例如JWT;第二部分是声明所使用的算法,例如HMAC SHA256
或RSA
等。Payload
:它包含了具体的用户信息,如用户ID、用户名、角色等,也可以包含自定义的其他信息。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
-
未登录测试
-
登录测试
-
首先登录获取
access
-
-
headers携带信息进行验证
-
-
(3)base64
解码数据
- 头部
- 解码后的结果是
{"alg":"HS256","typ":"JWT"}
,这是JWT的头部,指定了用于签名JWT的算法(在这个例子中是HS256)以及令牌的类型(JWT)。
- 解码后的结果是
- 载荷
- 这个载荷部分包含了关于这个JWT的一些声明信息,比如
token_type
表示这是一个访问令牌(access token),exp
是令牌的过期时间(UNIX时间戳),iat
是令牌的签发时间,jti
是令牌的唯一标识符,而user_id
是用户ID。
- 这个载荷部分包含了关于这个JWT的一些声明信息,比如
- 签名:这不是能直接看懂的信息
import base64info_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 LoginJWTrouter.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 = Bookfields = '__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 = BookModelSerializerpagination_class = CommonPaginationauthentication_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 hashlibclass 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 base64class JwtAuth(BaseAuthentication):def authenticate(self, request):# 获取jwtjwt = 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, jwtraise AuthenticationFailed("jwt信息验证失败。")except Exception as e:raise AuthenticationFailed("jwt信息有误。")