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. CentOS7.5环境下Docker环境搭建

    1. 安装wget工具: yum install wget -y 2. 使用wget工具从docker官网下载yum源: wget -P /etc/yum.repos.d/ https://downl ...

  2. Ubuntu系统中防火墙的使用和开放端口

    目录 Ubuntu系统 防火墙的使用和开放端口 1.安装防火墙 2.查看防火墙状态 3.开启.重启.关闭防火墙 4.Ubuntu添加开放.关闭端口 5.开放规定协议的端口 6.关闭指定协议端口 7.开 ...

  3. 跑马灯带你深入浅出TextView的源码世界

    一.背景 想必大家平时也没那么多时间是单独看源码,又或者只是单纯的看源码遇到问题还是不知道怎么从源码的角度解决. 但是大家平时开发过程中肯定会遇到这样或那样的小问题,通过百度.Google搜索都无果, ...

  4. 4月18日 python学习总结 异常处理、网络编程

    一. 异常 1.什么是异常 异常是错误发生的信号,程序一旦出错,如果程序中还没有相应的处理机制 那么该错误就会产生一个异常抛出来,程序的运行也随之终止 2.一个异常分为三部分: 1.异常的追踪信息 2 ...

  5. el-upload用form的方式多文件上传的方法

    使用el-upload组件遇到的坑. 1.第一种感觉最好,首先多个文件是一个http请求,另外还可以传除文件外其他的参数,但是没有进度条了. 发送请求的部分没有注释的部分是我分装了调后台的api,注释 ...

  6. 一个关于wait/notify与锁关系的探究

    wait/notify 机制是解决生产者消费者问题的良药.它的核心逻辑是基于条件变量的锁机制处理.所以,它们到底是什么关系?wait()时是否需要持有锁? notify()是否需要持有锁?先说答案:都 ...

  7. MAVEN setting文件

    <?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...

  8. Grep 命令有什么用? 如何忽略大小写? 如何查找不含 该串的行?

    是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印 出来. grep [stringSTRING] filename grep [^string] filename

  9. 学习RabbitMQ(二)

    MOM(message oriented middleware) 消息中间件(是在消息的传递过程中保存消息的容器,消息中间件再将消息从它的源中继到它的目标时,充当中间人的作用,队列的主要目的是提供路由 ...

  10. Java设置方法模板