Django一分钟:DRF快速实现JWT认证与RBAC权限校验
一、项目创建并实现JWT认证
1. 下载依赖
下载django
、djangorestframework
、djangorestframework_simplejwt
pip install django djangorestframework djangorestframework_simplejwt
2. 创建项目
- 启动Django项目
django-admin startproject <myproject>
cd myproject
用你实际的项目名称替换<myproject>
- 创建app
python manage.py startapp <myapp>
用你实际的app名称替换<myapp>
- 当前项目目录如下
myproject/
├── myproject/
| ├── __init__.py
| ├── settings.py
| ├── urls.py
| ├── wsgi.py
| └── asgi.py
├── myapp/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
└── manage.py
- 在配置文件中设置app
在settings.py
文件中配置好需要的app
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_simplejwt',
'myapp',
]
3. 配置JWT
# settings.py
from datetime import timedelta
# 添加在INSTALLED_APPS下
# 该配置用于指定默认使用的权限类和授权类
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
}
# 用于配置令牌过期时间等参数
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
'SLIDING_TOKEN_LIFETIME': timedelta(days=30),
'SLIDING_TOKEN_REFRESH_LIFETIME_LATE_USER': timedelta(days=1),
'SLIDING_TOKEN_LIFETIME_LATE_USER': timedelta(days=30),
}
4. 配置路由
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('', include('myapp.urls')),
]
5. 创建视图类并配置授权
创建视图类,并为视图类添加权限要求,这里我们先添加基本的授权要求,即要求用户必须在请求头中携带我们的JWT token才能访问相应的路径。
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
class BlogView(APIView):
permission_classes = (IsAuthenticated,)
authentication_classes = (JWTAuthentication,)
def get(self, request, *args, **kwargs):
return Response({'msg': 'success', 'detail': 'myblog'})
未新创建的视图创建urls.py文件
touch myapp/urls.py
创建完成后添加配置
from django.urls import path
from myapp.views import BlogView
urlpatterns = [
path('blog/', BlogView.as_view(), name='blog'),
]
6. 启动项目
数据库迁移
python manage.py migrate
启动项目
python manage.py createsuperuser
python manage.py runserver
二、测试JWT
在上一节的最后我们创建了一个管理员账户,假如为:
username: admin
password: admin123
请求授权接口的方法:
- 使用
djangorestframework_simplejwt
创建的token接口,请求token时要求我们使用POST方法,并在请求体中携带用户名和密码:{"username": "admin", "password": "admin123"}
。 - 如果请求成功,该接口会返回两个token,一个是
access_token
,另一个是refresh_token
。当请求需要授权权限的接口时,需要在请求头中携带access_token
。 access_token
的存活时间较短,refresh token的存活时间长,access_token
过期需要获取新令牌,获取新令牌需要携带在请求头中携带refresh_token
向../refresh/token
端口进行请求。- 所谓携带token指的是在请求头中添加
Authorization
字段,具体的格式时Authorization: Bearer <token>
,在代码中体现为{"Authorization": f"Bearer {token}"}
。
现在我们需要一个客户端来测试我们创建的后端服务,你可以通过postman创建测试请求,也可以通过http标准库、requests
或aiohttp
创建客户端进行测试,下面以aiohttp
为例:
import asyncio
from aiohttp import ClientSession
class Client:
"""测试客户端"""
def __init__(self):
self.url = "http://localhost:8000/"
self.user = {"username": "admin", "password": "admin123"}
self.session = ClientSession()
self.access_token = ""
self.refresh_token = ""
async def close(self):
await self.session.close()
async def get_token(self):
"""获取token"""
url = self.url + "auth/token/"
async with self.session.post(url, json=self.user) as response:
if response.status == 200:
data = await response.json()
if "access" in data and "refresh" in data:
self.access_token = data["access"]
self.refresh_token = data["refresh"]
print(f"access_token: {self.access_token}")
print(f"refresh_token: {self.refresh_token}")
else:
data.update({"error": "fail to get token"})
print(data)
else:
print(f"Error status code: {response.status}")
async def refresh_token(self):
"""刷新token"""
url = self.url + "auth/token/refresh/"
headers = {"Authorization": f"Bearer {self.refresh_token}"}
async with self.session.post(url, headers=headers) as response:
if response.status == 200:
data = await response.json()
if "access" in data:
self.access_token = data["access"]
print(f"access_token: {self.access_token}")
else:
data.update({"error": "fail to refresh token"})
print(data)
else:
print(f"Error status code: {response.status}")
async def get_blog(self):
"""获取博客"""
url = self.url + "blog/"
headers = {"Authorization": f"Bearer {self.access_token}"}
async with self.session.get(url, headers=headers) as response:
if response.status == 200:
print(await response.json())
else:
print(f"Error status code: {response.status}")
async def main():
client = Client()
await client.get_token()
await client.get_blog()
await client.close()
if __name__ == "__main__":
asyncio.run(main())
在前端项目中对接该接口,需要使用axios
或fetch
发起请求,可以把获取到的token存贮在localStorage
中,每次请求时携带授权请求头。
三、权限分配与验证
1. Django Auth基础知识
在本文中我们将使用Django自带的auth
系统来实现RBAC权限校验,在此之前需要了解一些关于Djangoauth
系统的基础知识。
注册Django的auth
应用,在初次进行migrate
数据库迁移的时候,Django会自动在数据库中创建5张表:用户、权限、组以及三者两两之间的关系表。这在RBAC权限管理系统的数据库表设计中非常常见。
- user
- group
- permission
- user_group
- group_permission
- user_permission
在使用Django的认证系统我们需要知道以下几件事:
- 我们可以自己在permission表中创建一些权限,但通常来说不需要,Django在执行数据库迁移时,会自动为已注册app的模型创建增、删、改、查四个权限。
- 我们可以为用户分配权限,本质上就是在
user_permission
关系表中创建一条数据。我们也可以创建一个组,你可以将组命名为“采购部门”,为组分配权限,被分配到这个组中的用户将自动获取这个组的权限。 - 通过
createsuperuser
创建的超级用户会拥有所有的权限(准确来说是自动通过权限认证),普通用户的权限需要自己分配。
2, 为用户分配权限
打开Django的shell控制台:
python manage.py shell
创建测试用户:
from django.contrib.auth.models import User
User.objects.create_user(username="test", email="test@qq.com",password="test123")
为新创建的用户分配权限view_blog
:
from django.contrib.auth.models import Permission
permission = Permission.objects.get(codename="view_blog")
user = User.objects.get(username="test")
user.user_permissions.add(permission)
user.save()
此时你可以通过一些数据库工具查询到,你的sqlite数据库中的user_permission关系表中新增了一条数据,这就表示我们为test
用户分配了view_blog
权限。
3. 为路视图类添加权限
我们可以通过drf自定义权限类的方式为APIView
整体添加权限限制:
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from apps.authorization.decorators import class_permission_required
from rest_framework.permissions import BasePermission
class ViewBlogP(BasePermission):
def has_permission(self, request, view):
return request.user.has_perm('auth.view_user')
class BlogView(APIView):
permission_classes = (IsAuthenticated, ViewBlogP)
authentication_classes = (JWTAuthentication,)
def get(self, request, *args, **kwargs):
return Response({'msg': 'success', 'detail': 'myblog'})
4.为视图类方法添加权限
django的permission_required
装饰器可以为视图方法创建权限要求,不过permission_required
不能直接在视图类的方法上直接使用,我们需要创建一个适配装饰器如下,你可以放置在utils.py
文件中:
# utils.py
from django.contrib.auth.decorators import permission_required
import functools
def class_permission_required(perm, login_url=None, raise_exception=False):
"""
适配装饰器使得permission_required装饰器在视图类的成员方法上也能使用
"""
original_decorator = permission_required(perm, login_url, raise_exception)
def adapter(view_method):
@functools.wraps(view_method)
def wrapped_view(self, request, *args, **kwargs):
def new_func(request, *args, **kwargs):
return view_method(self, request, *args, **kwargs)
decorated_func = original_decorator(new_func)
return decorated_func(request, *args, **kwargs)
return wrapped_view
return adapter
使用方法:
class BlogView(APIView):
permission_classes = (IsAuthenticated,)
authentication_classes = (JWTAuthentication,)
@class_permission_required('auth.view_user', raise_exception=True)
def get(self, request, *args, **kwargs):
return Response({'msg': 'success'})
关于此方法的更多细节请参考我的另一篇文章,欢迎订阅我的免费专栏Django一分钟。