Django之JWT库与SimpleJWT库的使用

JWT

JWT概述

JWT(JSON Web Token)是一种轻量级的认证和授权机制,它是基于 JSON 格式的标准,用于在网络应用程序或服务之间传递声明。

JWT官网:https://jwt.io/

特点:

javascript 复制代码
无状态:JWT 在服务器端不保存任何信息,因此可以跨多个请求进行身份验证。

自包含:JWT 包含了所有必要的信息,这样不需要去查询数据库或其他存储设施来验证用户身份。

可扩展性:由于 JWT 是基于 JSON 格式的标准,因此可以很容易地扩展以添加自定义数据。

强安全性:JWT 使用数字签名来验证消息的完整性和真实性,这样可以确保消息没有被篡改。

跨域支持:由于 JWT 是通过 HTTP 头部传输的,因此可以轻松地在跨域场景中使用。

主要应用:

JWT通常用于身份验证和授权。当用户成功登录时,服务器会生成一个 JWT,并将其返回给客户端。客户端随后将该令牌放入每个后续请求的Authorization标头中,以证明其已通过身份验证。服务器使用私钥来验证签名,并从 JWT 中提取必要的信息,例如用户 ID 和权限。

JWT组成:

JWT由三部分组成:头部、载荷和签名。

复制代码
1.头部包含关于生成 JWT的算法和类型的元数据。

2.载荷是JWT的主体内容,它包含有关用户或其他实体的信息,例如其 ID 或角色。

3.签名是使用密钥对头部和载荷进行加密的字符串,以确保JWT 在传输过程中不被篡改。

一个JWT Token字符串,由3部分组成,用.隔开

python 复制代码
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

头部(header)

JWT的头部承载两部分信息:

复制代码
声明类型,这里是jwt

声明加密的算法 通常直接使用HMAC SHA256
python 复制代码
{
  "alg": "HS256",
  "typ": "JWT"
}

将头部进行base64加密构成了第一部分

python 复制代码
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

载荷(payload)

载荷就是存放有效信息的地方。有效信息包含三个部分

1.标准中注册的声明(建议但不强制使用) :

makefile 复制代码
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

2.公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息。但不建议添加敏感信息,因为该部分在客户端可解密

3.私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息

python 复制代码
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1656239122
}

将其进行base64加密,得到JWT的第二部分

python 复制代码
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

签名(signature)

JWT的第三部分是一个签名信息,用来防止JWT token被伪造。

签名生成过程:

css 复制代码
1.服务器在生成jwt token时,会将header和payload字符串进行拼接,用.隔开

2.使用一个只有服务器知道的密钥对拼接后的内容进行加密,加密之后生成的字符串就是signature内容

签名验证过程:

css 复制代码
1.将客户端传递的jwt token中的header和payload字符串进行拼接,用.隔开

2.使用服务器自己的密钥对拼接之后的字符串进行加密

3.将加密之后的内容和将客户端传递的jwt token中signature进行对比,如果不一致,就说明jwt token是被伪造的
python 复制代码
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  256-bit-secret
)

注意:

复制代码
不要在jwt的payload部分存放敏感信息,客户端可解密得到

保护好secret密钥,使用https协议

Django使用JWT说明

在Python项目中使用JWT生成和校验Token,可以使用django-rest-framework-jwtdjangorestframework-simplejwt扩展来完成。

django-rest-framework-jwt

  • GitHub地址:https://github.com/jpadilla/django-rest-framework-jwt

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

djangorestframework-simplejwt

  • GitHub地址:https://github.com/jazzband/djangorestframework-simplejwt
  • 文档:https://django-rest-framework-simplejwt.readthedocs.io/en/latest/

注意:django-rest-framework-jwt不再维护,且适合低版本框架使用,若使用最新版本的Django和DRF如果使用JSON Web Token,项目启动会报错

python 复制代码
ImportError: Could not import 'rest_framework_jwt.authentication.JSONWebTokenAuthentication' for API setting 'DEFAULT_AUTHENTICATION_CLASSES'. ImportError: cannot import name 'smart_text' from 'django.utils.encoding'

jwt库的使用

安装依赖库

使用pip命令安装djangorestframework-jwt

python 复制代码
pip install djangorestframework-jwt

配置settings.py文件

添加以下内容到INSTALLED_APPS和REST_FRAMEWORK配置中

java 复制代码
INSTALLED_APPS = [
    ...
    'rest_framework',
]
python 复制代码
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # 引入JWT认证机制,当客户端将jwt token传递给服务器之后
        # 此认证机制会自动校验jwt token的有效性,无效会直接返回401(未认证错误)
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
}

# JWT扩展配置
JWT_AUTH = {
    # 设置生成jwt token的有效时间
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}

其他可选配置项:

python 复制代码
 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # 访问令牌的有效时间
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),    # 刷新令牌的有效时间
 
    'ROTATE_REFRESH_TOKENS': False,     # 若为True,则刷新后新的refresh_token有更新的有效时间
    'BLACKLIST_AFTER_ROTATION': True,   # 若为True,刷新后的token将添加到黑名单中, 
                                        # When True,'rest_framework_simplejwt.token_blacklist',should add to INSTALLED_APPS
 
    'ALGORITHM': 'HS256',       # 对称算法:HS256 HS384 HS512  非对称算法:RSA
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,      # if signing_key, verifying_key will be ignore.
    'AUDIENCE': None,
    'ISSUER': None,
 
    'AUTH_HEADER_TYPES': ('Bearer',),           # Authorization: Bearer <token>
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',   # if HTTP_X_ACCESS_TOKEN, X_ACCESS_TOKEN: Bearer <token>
    'USER_ID_FIELD': 'id',                      # 使用唯一不变的数据库字段,将包含在生成的令牌中以标识用户
    'USER_ID_CLAIM': 'user_id',
 
    # 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),   # default: access
    # 'TOKEN_TYPE_CLAIM': 'token_type',         # 用于存储令牌唯一标识符的声明名称 value:'access','sliding','refresh'
    #
    # 'JTI_CLAIM': 'jti',
    #
    # 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',     # 滑动令牌是既包含到期声明又包含刷新到期声明的令牌
    # 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),       # 只要滑动令牌的到期声明中的时间戳未通过,就可以用来证明身份验证
    # 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),  # path('token|refresh', TokenObtainSlidingView.as_view())

配置urls.py文件

配置urls.py文件,添加以下路由配置

java 复制代码
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token

urlpatterns = [
    path('api-token-auth/', obtain_jwt_token),
    path('api-token-refresh/', refresh_jwt_token),
    path('api-token-verify/', verify_jwt_token),
]

创建视图

创建自定义视图以生成JWT令牌

JWT扩展的提供了生成JWT Token的方法:

python 复制代码
from rest_framework_jwt.settings import api_settings

data = {"name": "Django", "name": "20"}
payload = api_settings.JWT_PAYLOAD_HANDLER(user)
jwt_token = api_settings.JWT_ENCODE_HANDLER(payload)
java 复制代码
from rest_framework_jwt.settings import api_settings

# 继承JSONWebTokenAPIView,使用CustomJWTSerializer进行序列化和验证
class CustomObtainJSONWebToken(JSONWebTokenAPIView):
    serializer_class = CustomJWTSerializer

class CustomJWTSerializer(JSONWebTokenSerializer):
	# validate 方法对提交的用户凭据进行验证,如果验证通过则生成 JWT token 并返回给客户端
    def validate(self, attrs):
    	# 从请求中获取用户名和密码
        credentials = {
            self.username_field: attrs.get(self.username_field),
            'password': attrs.get('password')
        }

		# 通过 Django 自带的 authenticate 函数进行认证
        if all(credentials.values()):
            user = authenticate(**credentials)

            if user:
            	# 检查该用户是否处于活跃状态,如果被禁用则返回错误信息
                if not user.is_active:
                    raise serializers.ValidationError('User account is disabled.')
				# 生成JWT的payload(负载)
                payload = api_settings.JWT_PAYLOAD_HANDLER(user)
                # 生成JWT token
                jwt_token = api_settings.JWT_ENCODE_HANDLER(payload)
                response_data = {
                    'token': jwt_token,
                }
                # 将生成的 JWT token 返回给客户端
                return response_data
            else:
                raise serializers.ValidationError('Unable to log in with provided credentials.')
        else:
            raise serializers.ValidationError('Must include "{username_field}" and "password".'.format(username_field=self.username_field))

配置权限

在视图中使用 @permission_classes 装饰器指定需要验证的权限。

java 复制代码
from rest_framework.permissions import IsAuthenticated

class CustomView(APIView):
    permission_classes = (IsAuthenticated,)

SimpleJWT库的使用

安装SimpleJWT库

通过pip命令安装SimpleJWT库:

python 复制代码
pip install djangorestframework-simplejwt

配置Django项目

在settings.py文件中添加以下配置:

python 复制代码
INSTALLED_APPS = [
    'rest_framework',
    'rest_framework_simplejwt',
]
python 复制代码
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        # 将全局权限控制方案设置为仅允许认证用户访问
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # JWT认证:使用SimpleJWT库提供的JWTAuthentication类来进行认证
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        # sesssion认证
        'rest_framework.authentication.SessionAuthentication',
        # 基本认证
        'rest_framework.authentication.BasicAuthentication',
    ),
}
SIMPLE_JWT = {
    # token有效时长(返回的 access 有效时长)
    'ACCESS_TOKEN_LIFETIME': datetime.timedelta(seconds=30),
    # token刷新的有效时间(返回的 refresh 有效时长)
    'REFRESH_TOKEN_LIFETIME': datetime.timedelta(seconds=20),
}

更多Simple JWT的配置参考: https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html

配置路由

配置项目级路由

python 复制代码
from django.urls import path, include, re_path

urlpatterns = [
    path('admin/', include(('apps.admin.urls', 'admin'), namespace="admin")),
]

配置子应用JWT视图的路由

python 复制代码
from django.urls import include, path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView

urlpatterns = [
    path('login/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    path('verify/', TokenVerifyView.as_view(), name='token_verify'),
]

上面视图路由使用的是使用JWT自身的类,也可以自定义视图实现相关功能

java 复制代码
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import AccessToken, RefreshToken
from rest_framework.permissions import IsAuthenticated

class TokenObtainView(APIView):
	# 视图需要被认证才能访问
    permission_classes = [IsAuthenticated]

    def post(self, request):
    	# 使用了SimpleJWT提供的AccessToken和RefreshToken类,为认证成功的用户生成对应的JWT令牌
        access_token = AccessToken.for_user(request.user)
        refresh_token = RefreshToken.for_user(request.user)

        return Response({
            'access_token': str(access_token),
            'refresh_token': str(refresh_token),
        })
java 复制代码
from django.urls import path
from .views import TokenObtainView

urlpatterns = [
    path('api/token/', TokenObtainView.as_view(), name='token_obtain'),
]

创建用户

使用Django自带用户认证系统,创建一个用户,用于登录

python 复制代码
import os

from django.test import TestCase

from django.contrib.auth.models import User
if not os.environ.get('DJANGO_SETTINGS_MODULE'):
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'meiduo_mall.settings')

import django
django.setup()


class MyTest(TestCase):
    User.objects.create_user(username='admin', password='admin')

接口测试

1.访问login 2.登录 登录接口,返回refresh和access值

复制代码
refresh用来刷新获取新的Token值

access用来请求身份认证的Token

access的过期时间参照配置ACCESS_TOKEN_LIFETIME

refresh的过期时间参照配置REFRESH_TOKEN_LIFETIME

当用refresh刷新后,access的过期时间等于:当前刷新的此刻时间+ACCESS_TOKEN_LIFETIME

3.Token校验 使用登录接口返回的access值,调用Token校验接口 4.刷新Token

使用登录接口返回的refresh值,调用Token刷新接口

当此短期访问令牌过期时,可以使用长期存在的刷新令牌来获取另一个访问令牌:

身份认证

使用返回的访问令牌来证明受保护视图的身份验证

python 复制代码
path('test/', TestView.as_view(), name='test'),
python 复制代码
from rest_framework.response import Response
from rest_framework.views import APIView


class TestView(APIView):

    def get(self, request):
        return Response("tet successfully")

在请求头加上Authorization,格式:Bearer token值 有空格

自定义令牌声明

自定义令牌需要视图创建一个子类,并为其相应的序列化程序创建一个子类

python 复制代码
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView


class MyTokenObtainPairSerializer(TokenObtainPairSerializer):

    def validate(self, attrs):
        data = super().validate(attrs)
        refresh = self.get_token(self.user)
        data['refresh'] = str(refresh)
        data['access'] = str(refresh.access_token)
        data['username'] = self.user.username
        return data


class MyTokenObtainPairView(TokenObtainPairView):
    serializer_class = MyTokenObtainPairSerializer

配置指向子类视图的url路由

python 复制代码
re_path(r'^login/$', views.MyTokenObtainPairView.as_view()),

手动创建令牌

为用户手动创建令牌,例如用户注册,注册完后直接返回token

python 复制代码
class MyCreateTokenView(APIView):
    permission_classes = [permissions.AllowAny]

    def get(self, request, *args, **kwargs):
        return Response("Get method is not supported !")

    def post(self, request, *args, **kwargs):
        refresh = RefreshToken.for_user(request.user)
        content = {
            'refresh': str(refresh),
            'access': str(refresh.access_token),
        }
        return Response(content)
python 复制代码
    re_path(r'^register/$', views.MyCreateTokenView.as_view()),

SimpleJWT的应用

实现用户身份验证和JWT token的生成

python 复制代码
from django.contrib.auth.models import User
from django.utils import timezone
from rest_framework import serializers
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.tokens import RefreshToken


class AuthSerializer(TokenObtainPairSerializer):
    id = serializers.IntegerField(label='用户ID', read_only=True)
    username = serializers.CharField(label='用户名')
    token = serializers.CharField(label='Token', read_only=True)
    refresh = serializers.CharField(label='Refresh', read_only=True)

	# 从attrs参数中获取到传入的用户名和密码
    def validate(self, attrs):
        # 获取username和password
        username = attrs['username']
        password = attrs['password']

        # 进行用户名和密码校验
        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            raise serializers.ValidationError('用户名或密码错误.')
        else:
            # 校验密码
            if not user.check_password(password):
                raise serializers.ValidationError('用户名或密码错误')

            # 给attrs中添加user属性,保存登录用户
            attrs['user'] = user

        return attrs

    def create(self, validated_data):
        # 获取登录用户user
        user = validated_data['user']

        # 设置最新登录时间
        user.last_login = timezone.now()
        user.save()

        refresh = RefreshToken.for_user(user)
        # 给user对象增加属性,保存jwt token的数据
        user.refresh = str(refresh)
        user.token = str(refresh.access_token)

        return user

登录实现

创建了AuthSerializer对象,并将POST请求的参数传入进行校验。如果校验通过,则调用serializer.save()方法生成JWT token,并将token数据作为响应返回。

python 复制代码
from rest_framework import status
from rest_framework.generics import CreateAPIView
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView

from apps.zd_admin.serializers.user import AuthSerializer


class LoginView1(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        # 获取参数并进行校验
        serializer = AuthSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # 调用序列化器类的create方法,实现服务器签发jwt token
        serializer.save()

        return Response(serializer.data, status=status.HTTP_201_CREATED)


class LoginView(CreateAPIView):
    permission_classes = [AllowAny]
    serializer_class = AuthSerializer
python 复制代码
urlpatterns = [
    re_path(r'^login/$', views.LoginView.as_view()),
]

注册实现

注册接口进行注册返回token认证信息也是同理

python 复制代码
 re_path(r'^register/$', views.RegisterView.as_view()),
python 复制代码
class RegisterView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        user = request.data
        username = user['username']
        password = user['password']

        # 保存注册数据
        try:
            user = User.objects.create_user(username=username, password=password)
        except DatabaseError:
            raise serializers.ValidationError('注册失败')

        refresh = RefreshToken.for_user(user)

        user = {"username": user.username, "token": str(refresh.access_token), "refresh": str(refresh)}
        return JsonResponse(user)

Token验证

使用登录、注册得到的Token随着请求携带进行测试

python 复制代码
re_path(r'^test/$', views.TestView.as_view()),
python 复制代码
class TestView(APIView):

    def get(self, request):
        return Response("tet successfully")
相关推荐
BullSmall5 小时前
第三方软件安全-Dependency‑Track Docker 部署
安全·docker·容器
AImatters5 小时前
国产算力底座,支撑油气储运数字化迈过关键分水岭
安全·cpu·算力·海光·油气储运
傲笑风6 小时前
【openvino】tinybert基于openvino服务化部署(四)
人工智能·python·自然语言处理·nlp·bert·openvino
维基框架6 小时前
WIKI 知识库 v1.1.1 正式发布
人工智能·python
Mr数据杨8 小时前
【Codex】用下载中心模块管理系统导出文件与下载任务
django·codex·项目开发
叠叠乐8 小时前
中国移动家庭云电脑window关闭所有安全脚本
安全
Jmyd01238 小时前
实训室的 3D 模型涉及肖像文物,数据安全与合规怎么做?
安全·3d·数据安全·虚拟实训
芯盾时代8 小时前
《金融业网络安全管理办法(征求意见稿)》全条款深度拆解(三)
网络·安全·网络安全
敢敢是只喵i9 小时前
一个本地 AI Agent 要操作多个门店或 SaaS 账号,应该怎样安全切换身份?
人工智能·安全·ai·系统架构·业界资讯
Acrel123410 小时前
DC800V 大规模落地,算力机房安全该如何保障
安全