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. DataTemplate的用法

    WPF 模板主要分为两大类:1.ControlTemplate: 控件的外观,也就是控件是什么样子.2.DataTemplate: 是数据内容的表现,一条数据显示成什么样子. (1)DataTemla ...

  2. emu8086 调用LED面板的方法

    一.实验要求 1.熟悉并掌握 EMU8086 汇编语言调试环境: 2.学习 8086 的指令系统,输入简单的指令,观察各寄存器.内存相关单元以及处理器标志位的变化(所有数据传送类指令,可参考教材用例) ...

  3. *****又错一道,不知道为啥。。。 ybt【例5.19】字符串判等

    [题目描述] 判断两个由大小写字母和空格组成的字符串在忽略大小写,且忽略空格后是否相等. [输入] 两行,每行包含一个字符串. [输出] 若两个字符串相等,输出YES,否则输出NO. 代码我觉得没啥问 ...

  4. CentOS7部署Bind

    镜像下载.域名解析.时间同步请点击 阿里巴巴开源镜像站 1.简介 DNS(Domain Name System),域名系统,因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问 ...

  5. CVE-2014-3120 (命令执行漏洞)

    环境配置 vulhub环境搭建 https://blog.csdn.net/qq_36374896/article/details/84102101 启动docker环境 cd vulhub-mast ...

  6. 分治FFT/NTT

    粘板子: #include<cstdio> #include<cstring> #include<algorithm> using namespace std; t ...

  7. cookies、sessionStorage和localStorage的区别

    cookies.sessionStorage和localStorage的区别 对比 特性 Cookie LocalStorage SessionStorage 数据的生命周期              ...

  8. C++获取设备 PID,VID 信息

    可直接编译(设置成:使用多字节字符集) 转来的,代码: /* http://www.experts-exchange.com/Programming/Editors_IDEs/Q_24506125.h ...

  9. Ubuntu修改登录密码

    Ubuntu 修改用户名或密码后无法登陆 解决方法 2017年09月01日 16:48:05 lee_3312 阅读数:2192   解决方法:进入recovery-mode修改登录信息 1)重启系统 ...

  10. Kerberos基本原理、安装部署及用法

    1. 概述 Kerberos是一种认证机制. 目的是,通过密钥系统为客户端/服务器应用程序提供强大的认证系统:保护服务器防止错误的用户使用,同时保护它的用户使用正确的服务器,即支持双向验证:Kerbe ...