前言
最近在编写项目时发现使用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
,它允许我们创建新的access
token,防止token过期;verify
,它用于校验我们生成的token是否正确。防止在访问过程中token被恶意修改后,用户仍能够访问需要权限的资源。
首先我们在accounts/urls.py
下,添加包含简单 JWT的TokenObtainPairView
和TokenRefreshView
视图的路由:
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
接口生成access
和refresh
令牌,提供首次身份验证令牌以及刷新令牌的凭证; - 使用
refresh
令牌通过refresh
接口更新access
令牌; - 最后让新生成的
refresh
令牌在verify
接口中进行验证。
使用JWT
在之前的项目中,我采用了rest_framework
自带的auth_token
模块,通过对用户token表的增删来控制登录状态,实际上这个方法并不是一个好的解决方案。我们不妨将JWT与之前使用的身份验证方式比较一下:
- auth_token 相对简单易用,是 Django 自带的身份验证方式,适合用于单个应用程序的简单身份验证场景。
- JWT 具有更大的灵活性和可扩展性,适用于分布式系统、跨域认证和多个服务之间的共享身份验证。
- auth_token 需要进行数据库查询,可能对数据库造成一定的负载,而 JWT 是无状态的,不需要进行数据库查询。
- JWT 的声明信息可以被解析和读取,但不能被修改,这提供了更高的安全性。
诚然,我之前制作的聊天室功能比较少,涉及的权限管理不复杂,使用JWT似乎大材小用。但是随着项目功能的功能的扩展,项目复杂度越来越高,那么使用JWT是非常有必要的。
既然需要修改验证方式,我们需要对原有的身份验证方式进行改动:
首先添加tokens.py
,这个文件用于存放生成access
和refresh
令牌的方法:
# 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