DRF JWT认证的第二个关键步骤是什么?

2026-05-19 20:501阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计3612个文字,预计阅读时间需要15分钟。

DRF JWT认证的第二个关键步骤是什么?

快速掌握JWT签发Token和认证,以下内容将助你总结:

1. JWT(JSON Web Token)是一种用于在网络应用间安全传输信息的开放标准。

2.使用JWT,你可以快速实现用户认证和授权。

3.DRF(Django REST Framework)自带JWT认证功能,同时支持自定义。

4.本指南将概述如何在Django中使用JWT进行认证。

目录

- DRF JWT认证(二)

- Django中快速使用JWT - 如何签发Token - 如何进行认证 - 定制Token返回格式

Django中快速使用JWT

如何签发Token

1. 在Django项目中安装`djangorestframework_simplejwt`。

2.在`settings.py`中添加`REST_FRAMEWORK`配置:

python

REST_FRAMEWORK={ 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ),}

3. 创建用户并登录,使用`Token`视图签发Token:

pythonfrom rest_framework.authtoken.views import ObtainToken

response=ObtainToken(username='your_username', password='your_password')token=response.data['token']

如何进行认证

1. 在`settings.py`中设置JWT的密钥:

pythonSIMPLE_JWT={ 'SECRET_KEY': 'your_secret_key',}

2. 使用JWT认证类:

pythonfrom rest_framework_simplejwt.authentication import JWTAuthentication

view.set_authentication_classes([JWTAuthentication()])

定制Token返回格式

1. 在`settings.py`中自定义Token返回格式:

pythonSIMPLE_JWT={ 'ACCESS_TOKEN_LIFETIME': 1 * 3600, # Token有效期 'REFRESH_TOKEN_LIFETIME': 7 * 24 * 3600, # 刷新Token有效期 'ROTATE_REFRESH_TOKENS': False, # 是否旋转刷新Token 'BLACKLIST_AFTER_ROTATION': True, # 刷新Token后是否黑名单旧Token 'UPDATE_LAST_LOGIN': False, # 更新最后登录时间 'ALGORITHM': 'HS256', # 签名算法 'SIGNING_KEY': 'your_secret_key', # 签名密钥 'VERIFYING_KEY': None, # 验证密钥 'AUTH_HEADER_TYPES': ('Bearer',), # 支持的认证头类型 'AUTH_HEADER_NAME': 'Authorization', # 认证头名称 'USER_ID_FIELD': 'id', # 用户ID字段 'USER_ID_CLAIM': 'user_id', # 用户ID声明 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), # Token类 'TOKEN_TYPE_CLAIM': 'token_type', # Token类型声明 'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser', # Token用户类}

以上内容将帮助你快速掌握JWT签发Token和认证。更多细节和示例,请参考官方文档。

快速上手JWT签发token和认证,有这一篇就够了,DRF自带的和自定义的都帮你总结好了,拿去用~

目录
  • DRF JWT认证(二)
    • django中快速使用JWT
      • 如何签发?
      • 如何认证?
      • 定制签发token返回格式
    • JWT源码分析
      • 签发源码分析
      • 认证源码分析
      • 签发源码内的其他两个类
    • 自定义User表,签发token
      • 普通写法,视图类写
      • 序列化类中写逻辑
    • 自定义认证类
    • 补充:HttpRequest.META

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-jwt:github.com/jpadilla/django-rest-framework-jwt

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

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

官网文档: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后返回更多数据需要我们自定制

步骤

DRF JWT认证的第二个关键步骤是什么?

  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 键。
*** 有错请指正,感谢~

本文共计3612个文字,预计阅读时间需要15分钟。

DRF JWT认证的第二个关键步骤是什么?

快速掌握JWT签发Token和认证,以下内容将助你总结:

1. JWT(JSON Web Token)是一种用于在网络应用间安全传输信息的开放标准。

2.使用JWT,你可以快速实现用户认证和授权。

3.DRF(Django REST Framework)自带JWT认证功能,同时支持自定义。

4.本指南将概述如何在Django中使用JWT进行认证。

目录

- DRF JWT认证(二)

- Django中快速使用JWT - 如何签发Token - 如何进行认证 - 定制Token返回格式

Django中快速使用JWT

如何签发Token

1. 在Django项目中安装`djangorestframework_simplejwt`。

2.在`settings.py`中添加`REST_FRAMEWORK`配置:

python

REST_FRAMEWORK={ 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ),}

3. 创建用户并登录,使用`Token`视图签发Token:

pythonfrom rest_framework.authtoken.views import ObtainToken

response=ObtainToken(username='your_username', password='your_password')token=response.data['token']

如何进行认证

1. 在`settings.py`中设置JWT的密钥:

pythonSIMPLE_JWT={ 'SECRET_KEY': 'your_secret_key',}

2. 使用JWT认证类:

pythonfrom rest_framework_simplejwt.authentication import JWTAuthentication

view.set_authentication_classes([JWTAuthentication()])

定制Token返回格式

1. 在`settings.py`中自定义Token返回格式:

pythonSIMPLE_JWT={ 'ACCESS_TOKEN_LIFETIME': 1 * 3600, # Token有效期 'REFRESH_TOKEN_LIFETIME': 7 * 24 * 3600, # 刷新Token有效期 'ROTATE_REFRESH_TOKENS': False, # 是否旋转刷新Token 'BLACKLIST_AFTER_ROTATION': True, # 刷新Token后是否黑名单旧Token 'UPDATE_LAST_LOGIN': False, # 更新最后登录时间 'ALGORITHM': 'HS256', # 签名算法 'SIGNING_KEY': 'your_secret_key', # 签名密钥 'VERIFYING_KEY': None, # 验证密钥 'AUTH_HEADER_TYPES': ('Bearer',), # 支持的认证头类型 'AUTH_HEADER_NAME': 'Authorization', # 认证头名称 'USER_ID_FIELD': 'id', # 用户ID字段 'USER_ID_CLAIM': 'user_id', # 用户ID声明 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), # Token类 'TOKEN_TYPE_CLAIM': 'token_type', # Token类型声明 'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser', # Token用户类}

以上内容将帮助你快速掌握JWT签发Token和认证。更多细节和示例,请参考官方文档。

快速上手JWT签发token和认证,有这一篇就够了,DRF自带的和自定义的都帮你总结好了,拿去用~

目录
  • DRF JWT认证(二)
    • django中快速使用JWT
      • 如何签发?
      • 如何认证?
      • 定制签发token返回格式
    • JWT源码分析
      • 签发源码分析
      • 认证源码分析
      • 签发源码内的其他两个类
    • 自定义User表,签发token
      • 普通写法,视图类写
      • 序列化类中写逻辑
    • 自定义认证类
    • 补充:HttpRequest.META

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-jwt:github.com/jpadilla/django-rest-framework-jwt

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

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

官网文档: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后返回更多数据需要我们自定制

步骤

DRF JWT认证的第二个关键步骤是什么?

  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 键。
*** 有错请指正,感谢~