DRF JWT认证(二)

上篇中对JWT有了基本的认知,这篇来略谈JWT的使用

签发:一般我们登录成功后签发一个token串,token串分为三段,头部,载荷,签名

1)用基本信息公司信息存储json字典,采用base64算法得到 头字符串
2)用关键信息存储json字典,采用base64算法得到 荷载字符串,过期时间,用户id,用户名
3)用头、体加密字符串通过加密算法+秘钥加密得到 签名字符串
拼接成token返回给前台

认证:根据客户端带token的请求 反解出 user 对象

1)将token按 . 拆分为三段字符串,第一段 头部加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间是安全信息,确保token没过期
3)再用 第一段 + 第二段 + 加密方式和秘钥得到一个加密串,与第三段 签名字符串 进行比较,通过后才能代表第二段校验得到的user对象就是合法的登录用户

JWT可以使用如下两种:

djangorestframework-jwtdjangorestframework-simplejwt

djangorestframework-jwthttps://github.com/jpadilla/django-rest-framework-jwt

djangorestframework-simplejwthttps://github.com/jazzband/djangorestframework-simplejwt

区别https://blog.csdn.net/lady_killer9/article/details/103075076

官网文档https://jpadilla.github.io/django-rest-framework-jwt/

django中快速使用JWT

导入pip3 install djangorestframework-jwt

如何签发?

步骤

  1. 路由中配置

    from rest_framework_jwt.views import obtain_jwt_token
    urlpatterns = [
    path('login/', obtain_jwt_token),
    ]
  2. 使用接口测试工具发送post请求到后端,就能基于auth的user表签发token

    {
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTUyNDY2MiwiZW1haWwiOiIifQ.P1Y8Z3WhdndHoWE0PjW-ygd53Ng0T46U04oY8_0StwI"
    }

base64反解

import base64

# 第一段
s1 = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'
print(base64.b64decode(s1))
# b'{"typ":"JWT","alg":"HS256"}' # 第二段
s2 = b'eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTUyNDY2MiwiZW1haWwiOiIifQ=='
print(base64.b64decode(s2))
# b'{"user_id":1,"username":"Hammer","exp":1649524662,"email":""}'
# 我们发现第二段可以反解密出用户信息,是有一定的风险,可以使用,但是不能更改,就好比你的身份证丢了,别人可以在你不挂失的情况下去网吧上网 '''第三段不能不能反解,只能做base64解码,第三段使用base64编码只是为了统一格式'''

如何认证?

我们没有认证的时候,直接访问接口就可以返回数据,比如访问/books/发送GET请求就可以获取所有book信息,那么现在添加认证,需要访问通过才能访问才更合理

步骤

  • 视图中配置,必须配置认证类权限类

  • 访问需要在请求头中使用,携带签发的token串,格式是:

    key是Authorization
    value是jwt token串
    Authorization : jwt token串
    '''注意jwt和token串中间有空格'''

视图

from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
class BookView(GenericViewSet,ListModelMixin):
···
# JSONWebTokenAuthentication :rest_framework_jwt模块写的认证类
authentication_classes = [JSONWebTokenAuthentication,]
# 需要配合一个权限类
permission_classes = [IsAuthenticated,]
···

定制签发token返回格式

JWT默认的配置是,我们登录成功后只返回一个token串,这也是默认的配置,我们如果想签发token后返回更多数据需要我们自定制

步骤

  1. 写一个函数,返回什么格式,前端就能看见什么格式
  2. 在配置文件中配置JWT_AUTH

utils.py

# 定义签发token(登陆接口)返回格式
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code': 100,
'msg': "登陆成功",
'token': token,
'username': user.username
}

settings.py

JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
}

JWT源码分析

签发源码分析

1.入口:path('login/', obtain_jwt_token)

2.obtain_jwt_token--->obtain_jwt_token = ObtainJSONWebToken.as_view()
ObtainJSONWebToken.as_view(),其实就是一个视图类.as_view() 3.ObtainJSONWebToken类源码
'''
class ObtainJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
''' 4.登录签发token肯定需要一个post方法出来,但是ObtainJSONWebToken类内没有父类JSONWebTokenAPIView写了post方法:
def post(self, request, *args, **kwargs):
# 获取数据:{'username': 'Hammer', 'password': '7410'}
serializer = self.get_serializer(data=request.data)
# 校验
if serializer.is_valid():
user = serializer.object.get('user') or request.user # 获取用户
token = serializer.object.get('token') # 获取token
response_data = jwt_response_payload_handler(token, user, request)
# {'code': 100, 'msg': '登陆成功', 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTU4MTU0NiwiZW1haWwiOiIifQ.2oAjKQ90SV2S9Yxrwppo7BwAOv0xFW4i4AHHBX5Cg2Q', 'username': 'Hammer'}
response = Response(response_data)
if api_settings.JWT_AUTH_COOKIE:
···
return response # 定制什么返回什么 return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 5.get_serializer(data=request.data)如何获取到用户数据?
JSONWebTokenSerializer序列化类中全局钩子中获取当前登录用户和签发token
···
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
···

签发总结

从obtain_jwt_token开始, 通过ObtainJSONWebToken视图类处理,其实是父类JSONWebTokenAPIView的post方法通过传入的用户名和密码处理获取当前用户,签发了token

认证源码分析

# 视图类内认证类搭配权限类使用
authentication_classes = [JSONWebTokenAuthentication, ]
permission_classes = [IsAuthenticated, ]

我们在前面写过,如果需要认证肯定需要重写authenticate方法,这里从列表内的认证类作为入口分析:

'''认证类源码'''
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
www_authenticate_realm = 'api' def get_jwt_value(self, request):
# 获取传入的Authorization:jwt token串,然后切分
auth = get_authorization_header(request).split()
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
# 获取不到的情况
if not auth:
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None # 直接返回None,也不会报错,所以必须搭配权限类使用 ··· return auth[1] # 一切符合判断条件,通过split切分的列表索引到token串
'''认证类父类源码'''
def authenticate(self, request):
jwt_value = self.get_jwt_value(request) # 获取真正的token,三段式,上面分析
if jwt_value is None: # 如果没传token,就不认证了,直接通过,所以需要配合权限类一起用
return None try:
payload = jwt_decode_handler(jwt_value)# 验证签名
except jwt.ExpiredSignature:
msg = _('Signature has expired.') # 过期了
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')# 被篡改了
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()# 不知名的错误 user = self.authenticate_credentials(payload) return (user, jwt_value)

签发源码内的其他两个类

导入from rest_framework_jwt.views import obtain_jwt_token,refresh_jwt_token,verify_jwt_token

obtain_jwt_token = ObtainJSONWebToken.as_view()  # 获取token
refresh_jwt_token = RefreshJSONWebToken.as_view() # 更新token
verify_jwt_token = VerifyJSONWebToken.as_view() # 认证token

refresh_jwt_token用法

# 配置文件
JWT_AUTH = {
'JWT_ALLOW_REFRESH': True
}
# 路由
path('refresh/', refresh_jwt_token)

verify_jwt_token用法

path('verify/', verify_jwt_token),

自定义User表,签发token

普通写法,视图类写

上面我们写道,签发token是基于Django自带的auth_user表签发,如果我们自定义User表该如何签发token,如下:

视图

# 自定义表签发token
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings
from app01 import models
class UserView(ViewSetMixin,APIView):
@action(methods=['POST'],detail=False)
def login(self,request):
username = request.data.get('username')
password = request.data.get('password')
user = models.UserInfo.objects.filter(username=username,password=password).first()
response_dict = {'code':None,'msg':None}
# 源码copy错来使用
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
if user:
'''
签发token去源码copy过来使用
'''
# 载荷字典
payload = jwt_payload_handler(user)
print(payload)
# {'user_id': 1, 'username': 'Hammer', 'exp': datetime.datetime(2022, 4, 10, 13, 13, 15, 363206), 'email': '123@qq.com', 'orig_iat': 1649596095}
# 通过荷载得到token串
token = jwt_encode_handler(payload)
response_dict['code'] = 2000
response_dict['msg'] = '登录成功'
response_dict['token'] = token else:
response_dict['code'] = 4001
response_dict['msg'] = '登录失败,用户名或密码错误'
return Response(response_dict)

模型

# user表
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
email = models.EmailField()

路由

from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('user',views.UserView,'user')

序列化类中写逻辑

源码中签发校验都在序列化类中完成,这种写法确实比较常用,我们来使用这种方式自定义,将上面视图的校验逻辑写到序列化类中,这个序列化类只用来做反序列化,这样我们就可以利用 反序列化 的字段校验功能来帮助我们校验(模型中的条件),但是我们不做保存操作

视图

from .serializer import UserInfoSerializer
class UserView(ViewSetMixin,APIView):
@action(methods=['POST'],detail=False)
def login(self,request):
# 如果想获取什么这里可以实例化对象写入,比如request
serializer = UserInfoSerializer(data=request.data, context={'request': request})
response_dict = {'code':None,'msg':None}
# 校验,局部钩子,全局钩子都校验完才算校验通过,走自己的校验规则
if serializer.is_valid():
# 从序列化器对象中获取token和username
token = serializer.context.get('token')
username = serializer.context.get('username') response_dict['code']=2000
response_dict['msg']='登录成功'
response_dict['token'] = token
response_dict['username'] = username
else:
response_dict['code'] = 4001
response_dict['msg'] = '登录失败,用户名或密码错误' return Response(response_dict)

序列化器

from rest_framework.exceptions import ValidationError

class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = UserInfo
# 根据模型里的字段写
fields = ['username', 'password'] # 全局钩子
def validate(self, attrs):
# attrs是校验过的字段,这里利用
username = attrs.get('username')
password = attrs.get('password')
user = UserInfo.objects.filter(username=username, password=password).first() from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER if user: # 登录成功 payload = jwt_payload_handler(user) # 得到荷载字典
token = jwt_encode_handler(payload) # 通过荷载得到token串
# 将token放入context字典中
self.context['token'] = token
self.context['username'] = username
# context是serializer和视图类沟通的桥梁
print(self.context.get('request').method)
else: # 登录失败
raise ValidationError('用户名或密码错误')
return attrs

总结

需要我们注意的是,context只是我们定义的字典,比如上面写到的实例化序列化类中指定的context,那么就可以从序列化类打印出请求的方法,context是序列化类和视图类沟通的桥梁

自定义认证类

auth.py

import jwt
from django.utils.translation import ugettext as _
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.settings import api_settings
from .models import UserInfo class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
# 第一步、取出传入的token,从请求头中取 # 这里注意,获取的时候格式为:HTTP_请求头的key大写
jwt_value = request.META.get('HTTP_TOKEN')
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
# 验证token:验证是否过期,是否被篡改,是否有其他未知错误,从源码copy过来使用
if jwt_value:
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
msg = _('Unknown Error.')
raise exceptions.AuthenticationFailed(msg) # 第二部、通过payload获得当前登录用户,本质是用户信息通过base64编码到token串的第二段载荷中
user = UserInfo.objects.filter(pk=payload['user_id']).first()
# 返回user和token
return (user, jwt_value)
else:
raise AuthenticationFailed('No token was detected')

视图

from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializer import BookSerializer
from .auth import JWTAuthentication
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [JWTAuthentication,]

序列化器

class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'

路由

from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('book',views.BookView,'book')

正常的情况

不携带token的情况

总结

  • 从请求头中获取token,格式是HTTP_KEY,key要大写
  • 认证token串没有问题,返回用户信息从载荷中获取,本质是用户信息通过base64编码到token串的第二段载荷中,可以通过base64解码获取到用户信息

补充:HttpRequest.META

HTTP请求的数据在META中

HttpRequest.META

   一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器,下面是一些示例:
  取值: CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
CONTENT_TYPE —— 请求的正文的MIME 类型。
HTTP_ACCEPT —— 响应可接收的Content-Type。
HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
HTTP_HOST —— 客服端发送的HTTP Host 头部。
HTTP_REFERER —— Referring 页面。
HTTP_USER_AGENT —— 客户端的user-agent 字符串。
QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
REMOTE_ADDR —— 客户端的IP 地址。
REMOTE_HOST —— 客户端的主机名。
REMOTE_USER —— 服务器认证后的用户。
REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。
SERVER_NAME —— 服务器的主机名。
SERVER_PORT —— 服务器的端口(是一个字符串)。
  从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 META 的键时,
都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_ 前缀。
所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。

*** 有错请指正,感谢~

DRF JWT认证(二)的更多相关文章

  1. DRF JWT认证(一)

    为什么要使用JWT认证?构成和原理又是什么?怎么还有Base64的事?我都写了

  2. 9) drf JWT 认证 签发与校验token 多方式登陆 自定义认证规则反爬 admin密文显示

    一 .认证方法比较 1.认证规则图 django 前后端不分离 csrf认证 drf 前后端分离 禁用csrf 2. 认证规则演变图 数据库session认证:低效 缓存认证:高效 jwt认证:高效 ...

  3. drf JWT认证模块与自定制

    JWT模块 在djangorestframework中,有一款扩展模块可用于做JWT认证,使用如下命令进行安装: pip install djangorestframework-jwt 现在,就让我们 ...

  4. drf框架中jwt认证,以及自定义jwt认证

    0909自我总结 drf框架中jwt 一.模块的安装 官方:http://getblimp.github.io/django-rest-framework-jwt/ 他是个第三方的开源项目 安装:pi ...

  5. drf组件之jwt认证

    drf组件之jwt认证模块 一.认证规则 全称:json web token 解释:加密字符串的原始数据是json,后台产生,通过web传输给前台存储 格式:三段式 - 头.载荷.签名 - 头和载荷才 ...

  6. drf认证组件、权限组件、jwt认证、签发、jwt框架使用

    目录 一.注册接口 urls.py views.py serializers.py 二.登录接口 三.用户中心接口(权限校验) urls.py views.py serializers.py 四.图书 ...

  7. drf认证组件(介绍)、权限组件(介绍)、jwt认证、签发、jwt框架使用

    目录 一.注册接口 urls.py views.py serializers.py 二.登录接口 三.用户中心接口(权限校验) urls.py views.py serializers.py 四.图书 ...

  8. DRF之JWT认证

    一.JWT认证 JWT构成 JWT分为三段式:头.体.签名(head.payload.sgin) 头和体是可逆加密的,让服务器可以反解析出user对象,签名是不可逆加密,保证整个token的安全性的. ...

  9. ASP.NET Core 基于JWT的认证(二)

    ASP.NET Core 基于JWT的认证(二) 上一节我们对 Jwt 的一些基础知识进行了一个简单的介绍,这一节我们将详细的讲解,本次我们将详细的介绍一下 Jwt在 .Net Core 上的实际运用 ...

随机推荐

  1. 高级IO模型之kqueue和epoll

    目录 简介 block IO和nonblocking IO IO多路复用和select poll epoll kqueue epoll和kqueue的优势 简介 任何一个程序都离不开IO,有些是很明显 ...

  2. 在/etc/docker/下 创建daemon.json重新加载后docker无法启动问题 /etc/docker/daemon.json编辑不了 找不到

    可能产生这个问题的原因至少有以下几个: 编辑daemon.json步骤不对:想要创建并编辑daemon.json,应该是先启动docker服务,此时系统自动产生/etc/docker目录,此时cat生 ...

  3. 如何将docker 镜像上传到docker hub仓库

    如何将docker 镜像上传到docker hub仓库 目录 如何将docker 镜像上传到docker hub仓库 背景 1.注册docker hub账号 2.docker hub上创建仓库 3.d ...

  4. SQL注入及防止SQL注入

    •SQL注入 SQL注入是通过操作输入来修改事先定义好的SQL语句,对用户输入的字符串进行过滤,转义,限制或处理不严谨,导致用户可以通过输入精心构造的字符串去非法获取到数据库中的数据,以达到执行代码对 ...

  5. 【混合编程】C/C++调用Fortran的DLL

    [混合编程]C/C++调用Fortran的DLL 以一个简单的加法器为例,介绍C/C++调用Fortran语言DLL的操作过程 一.Fortran操作 1.1 Fortran代码 首先是加法功能的实现 ...

  6. spinlock 设计的初衷,当只有单核时是否还需要锁

    自旋锁,的设计初衷是什么,是为了解决什么问题.如果只有一个cpu,并且是单核,那是否还需要用到自旋锁.

  7. HashMap:为什么容量总是为2的n次幂

    HashMap:为什么容量总是为2的n次幂1).HashMap是根据key的hash值决定key放到哪个桶中,通过tab[i = (n - 1) & hash]公式计算得出 这里的n是Hash ...

  8. 为什么以iPhone6为标准的设计稿的尺寸是以750px宽度来设计的呢?

    iPhone6的满屏宽度是375px,而iPhone6采用的视网膜屏的物理像素是满屏宽度的2倍,也就是dpr(设备像素比)为2, 并且设计师所用的PS设计软件分辨率和像素关系是1:1.所以为了做出的清 ...

  9. Eureka和Zookeeper区别?

    (1)Eureka取CAP的AP,注重可用性,Zookeeper取CAP的CP注重一致性. (2)Zookeeper在选举期间注册服务瘫痪,虽然服务最终会恢复,但选举期间不可用. (3)eureka的 ...

  10. Maria DB数据库基础知识

    Maria DB连接 与MariaDB建立连接的一种方法是在命令提示符下使用mysql二进制文件. Maria DB命令行登录数据库服务: mysql -u root -p -- 换行输入密码 上面给 ...