在Django中使用JWTSimple JWT库的使用

2023年 9月 22日 110.5k 0

前言

最近在编写项目时发现使用auth_token的方式实现身份验证时,发现它有不少缺点:

  • 需要使用token的时候,随着网站功能的增多,需要多次操作数据库做身份验证;
  • 单token,没有刷新、验证机制,安全性能低;
  • ...

在寻找解决方案的过程中发现了JWT(JSON Web Tokens)的身份验证方式,学习之余记录下来,加深印象的同时分享给大家。

关于JWT的解读网络上有很多,我在这篇文章中只讲在Django中怎么使用JWT,而不深入其原理的介绍与解释。

JWT官网:jwt.io/

在Django项目中使用JWT

我们可以在Django项目中使用rest_framework提供的Simple JWT来生成JWT。

本例基于我之前发布的聊天室课程Django项目实例实现。

安装Simple JWT

pip install djangorestframework-simplejwt

在Django项目中配置JWT

settings.py中的REST_FRAMEWORK自定义配置项的添加默认身份验证类下,添加Simple JWT

# server/chat_api/settings.py
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework.authentication.SessionAuthentication",
        # Token令牌验证
        "rest_framework.authentication.TokenAuthentication",
        # 添加Simple JWT
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ),
}

我们还可以自定义配置Simple JWT

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(hours=2),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=1),
    "ALGORITHM":'HS256',
    # 身份验证的视图将接受的授权标头类型。token格式:"Bearer "
    "AUTH_HEADER_TYPES": ("Bearer", )
}

其他详细配置可参照官方文档

完成基本配置之后我们需要做的下一件事是添加我们用于获取token、刷新token以及验证token的视图。

这个视图可以刷新我们的访问令牌(token)。因此当我们使用JWT身份验证时,提供了以下token:

  • access,用于请求身份验证的token。它允许我们访问受保护的的应用程序的某一部分资源;
  • refresh,它允许我们创建新的accesstoken,防止token过期;
  • verify,它用于校验我们生成的token是否正确。防止在访问过程中token被恶意修改后,用户仍能够访问需要权限的资源。

首先我们在accounts/urls.py下,添加包含简单 JWT的TokenObtainPairViewTokenRefreshView视图的路由:

from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
    TokenVerifyView,
)
from . import views

urlpatterns = [
    path("signup/", views.SignUpView.as_view(), name="signup"),
    path("login/", views.LoginView.as_view(), name="login"),
    path("logout/", views.Logout, name="logout"),
    path("jwt/create/", TokenObtainPairView.as_view(), name="jwt_create"),
    path("jwt/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
    path("jwt/verify/", TokenVerifyView.as_view(), name="token_verify"),
]

verify的存在主要用于验证token是否合法。在接下来的接口编写中没有使用,在这就不做详细介绍。

Simple JWT的工作流程

接口编写完成之后,我们使用postman来测试一下接口:

从这个接口中我们可以得到访问token和刷新token。

接下来我们测试一下刷新token的接口:

复制refresh令牌到请求的json中。

返回一个新的access令牌。在令牌刷新以后,可以使用verify接口验证新生成的access令牌是否有效:

结果显示200,请求成功。虽然没有任何返回值,但这个结果表明了该access令牌是有效的。上面我使用的视图是Simple JWT提供的TokenVerifyView模版视图,我们可以利用Python的继承覆写特性自定义该方法,有兴趣的同学可以查看源码自己动手尝试一下。

简单总结一下工作流程:

  • 通过create接口生成accessrefresh令牌,提供首次身份验证令牌以及刷新令牌的凭证;
  • 使用refresh令牌通过refresh接口更新access令牌;
  • 最后让新生成的refresh令牌在verify接口中进行验证。

使用JWT

在之前的项目中,我采用了rest_framework自带的auth_token模块,通过对用户token表的增删来控制登录状态,实际上这个方法并不是一个好的解决方案。我们不妨将JWT与之前使用的身份验证方式比较一下:

  • auth_token 相对简单易用,是 Django 自带的身份验证方式,适合用于单个应用程序的简单身份验证场景。
  • JWT 具有更大的灵活性和可扩展性,适用于分布式系统、跨域认证和多个服务之间的共享身份验证。
  • auth_token 需要进行数据库查询,可能对数据库造成一定的负载,而 JWT 是无状态的,不需要进行数据库查询。
  • JWT 的声明信息可以被解析和读取,但不能被修改,这提供了更高的安全性。

诚然,我之前制作的聊天室功能比较少,涉及的权限管理不复杂,使用JWT似乎大材小用。但是随着项目功能的功能的扩展,项目复杂度越来越高,那么使用JWT是非常有必要的。

既然需要修改验证方式,我们需要对原有的身份验证方式进行改动:

首先添加tokens.py,这个文件用于存放生成accessrefresh令牌的方法:

# accounts/tokens.py

# 生成jwt tokens
from rest_framework_simplejwt.tokens import RefreshToken
# 获取用户
from django.contrib.auth import get_user_model

User = get_user_model()


def create_jwt_pair_for_user(user: User):
    refresh = RefreshToken.for_user(user)

    tokens = {"access": str(refresh.access_token), "refresh": str(refresh)}

    return tokens

create_jwt_pair_for_user函数内部,我们通过获取到的user对象使用 RefreshToken.for_user() 方法创建了一个刷新令牌(refresh),它会生成一个包含用户信息的 JWT 对。

接下来,我们将生成的令牌拼装成一个字典 tokens,其中包含了两个键值对:

  • "access" 键对应的值是刷新令牌对象的访问令牌(access token),使用 str() 方法将其转换为字符串形式。
  • "refresh" 键对应的值是刷新令牌对象本身,同样使用 str() 方法将其转换为字符串形式。

最后,我们返回了包含访问令牌和刷新令牌的字典 tokens。

接下来修改登录函数,调用create_jwt_pair_for_user函数:

# accounts/views.py
from .tokens import create_jwt_pair_for_user

class LoginView(APIView):
    permission_classes = []

    def post(self, request):
        email = request.data.get("email")
        password = request.data.get("password")

        user = authenticate(email=email, password=password)

        current_user = User.objects.filter(email=email)

        if user is not None:
        	# 生成JWT tokens组
            tokens = create_jwt_pair_for_user(user)
            response = {
                "message": "登录成功!",
                "errors": [],
                "user": {
                    "user_id": user.id,
                    "name": user.username,
                    "email": user.email,
                    "token": tokens,
                },
            }
            return Response(data=response, status=status.HTTP_200_OK)
        else:
            return Response(data={"message": "无效的邮箱或密码"})

    def get(self, request):
        content = {"user": str(request.user), "auth": str(request.auth)}

        return Response(data=content, status=status.HTTP_200_OK)

当用户登录时,将这个用户的信息对象传入这个函数,就能得到一个包含访问令牌和刷新令牌的字典。这些令牌可以用于身份验证和授权,以及在需要时进行令牌刷新(通过使用刷新令牌获取新的访问令牌)。

修改完成!让我们测试一下查看效果:

非常好!接下来让我们测试一下需要身份验证的接口。

使用返回的access令牌来进行受保护视图的身份验证:

由于设置了请求头格式,我们需要在请求中添加Authorization:"Bearer [token]"

聊天室列表:

聊天室详情:

写在最后

完整实例可参照我之前发布的聊天室课程进行操作:

  • 课程地址:Part 1,Part 3
  • 完整源码:github

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论