本文将介绍如何使用DRF快速实现JWT授权、RBAC权限校验。此外,文中将会对一些关键配置做讲解,帮助你理解Django、DRF快速开发的背后框架都为我们做了什么。
一、项目创建
本章节将使用包管理工具poetry
来快速搭建django项目,如果你不熟悉poetry
请自行创建python虚拟环境并下载以下依赖:django
、djangorestframework
、djangorestframework-simplejwt
,相信这并不是什么难事。
- 创建项目目录
python
mkdir djangodemo
cd djangodemo
- 初始化并设置国内源
python
poetry init --no-interaction
poetry source add aliyun https://mirrors.aliyun.com/pypi/simple
- 添加依赖
- django
- djangorestframework
- djangorestframework-simplejwt
python
poetry add django djangorestframework djangorestframework-simplejwt
- 创建django项目
python
poetry run django-admin startproject djangodemo
创建项目时需要启动虚拟环境,
poetry run
命令可以启动虚拟环境
- 调整目录,经过上述步骤所创建的项目目录如下:
python
/djangodemo
├── .venv
├── djangodemo # 多余的目录
│ ├── djangodemo # 项目目录
│ │ ├── settings.py
│ │ ├── urls.py
│ │ ├── wsgi.py
│ │ └── ...
│ └── manage.py
│
├── poetry.lock
└── pyproject.toml
可以看到django-admin创建的项目目录外,存在一层多余的目录,我们需要做一些调整,把内部真正的项目目录移出来,把外面多余的项目目录删除掉,调整后的项目目录将如下:
python
/djangodemo
├── .venv
├── manage.py
├── djangodemo # 项目目录
│ ├── settings.py
│ ├── urls.py
│ ├── wsgi.py
│ └── ...
├── poetry.lock
└── pyproject.toml
- 创建APP,示例中我们创建名为
API
的APP:
python
poetry run python manage.py startapp api
如果你使用pycharm建议直接在pycharm的
manage.py
控制台中进行app创建,pycharm会自动完成settings.py的配置以及其它的一些辅助配置。
- 注册drf以及我们创建的app:
python
# settings.py
INSTALLED_APPS = [
...
"rest_framework",
"api.apps.ApiConfig",
]
二、模型创建
1. 模型创建
我们创建一个简单的Order
(订单)模型,并以User
模型为外键,这里的User
来自django.contrib.auth.models
是django内置的一个用户模型,用于进行授权登录。
python
# api.models.py
from django.db import models
from django.contrib.auth.models import User
class Order(models.Model):
name = models.CharField(max_length=255)
notes = models.CharField(max_length=255)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return self.name
执行数据库迁移
python
python manage.py
makemigration api
migrate
2. django认证授权系统知识补充
如果你对django的认证和授权系统非常了解请跳过此小节。
在本文中我们将使用Django自带的auth
系统来实现RBAC权限校验,在此之前需要了解一些关于Djangoauth
系统的基础知识。
完成APP注册后,初次进行migrate
数据库迁移的时,除了我们APP中的Order
表之外,Django会自动在数据库中创建5张表:用户、权限、组以及三者两两之间的关系表,它们被定义在Django内置的auth
APP内。五表设计在RBAC权限管理中非常经典:
- user
- group
- permission
- user_group
- group_permission
- user_permission
使用Django的认证系统我们还需要知道以下几件事:
- 可以自己在
permission
表中创建权限,但通常来说不需要。Django在执行数据库迁移时会自动在permission
表中为已注册APP的所有模型创建增、删、改、查四个权限。比如在我们的APP中只有一个Order
模型,Django将自动为Order
模型在permission
表中创建四个权限:add_order
、change_order
、delete_order
、view_order
。 - 我们可以为用户分配权限,本质上就是在
user_permission
关系表中创建一条数据。我们也可以创建一个组,为组分配权限,组中的用户也将持有相同的权限。 - 通过
createsuperuser
创建的超级用户会拥有所有的权限(准确来说是自动跳过权限认证)。
三、序列化器和视图集
1. 序列化器
为Order
模型创建序列化器如下:
python
# 创建api/serializers.py
from rest_framework import serializers
from api.models import Order
class OrderSerializer(serializers.ModelSerializer):
created_by = serializers.StringRelatedField(read_only=True)
class Meta:
model = Order
fields = ['id', 'name', 'notes', 'created_by']
read_only_fields = ['id', 'created_by']
2. 视图集
接着使用模型和序列化器创建视图集如下:
python
# api/views.py
from rest_framework import viewsets
from rest_framework.permissions import DjangoModelPermissions
from api.models import Order
from api.serializers import OrderSerializer
class OrderViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
permission_classes = (DjangoModelPermissions,)
def perform_create(self, serializer):
# 将当前用户注入模型
serializer.save(created_by=self.request.user)
注意在OrderChangeViewSet
视图集中我们重写了CreateModelMixin
提供的perform_create
方法,用于将请求的用户信息注入到Order
模型中,以实现自动记录Order
的创建者。
3. 关于DjangoModelPermissions
在上面的视图视图集中我们给permission_classes
属性配置了DjangoModelPermissions
类,无需其它操作,这就已经完成了权限配置。本小节将讲解DjangoModelPermissions
类背后的原理。
DRF的视图集要求我们实现list
、create
、destroy
等方法,路由会将其自动映射到get
、post
、delete
等处理方法上。
DjangoModelPermissions
实际上也是类似的做法,它把不同种类的"权限要求"映射到逻辑上对应的请求处理方法上,如:add_xxxx -> POST
、change_xxx -> PUT
、delete_xxx -> DELETE
等等。在DjangoModelPermissions
的源码中的perms_map
属性清晰的定义了这一点:
python
class DjangoModelPermissions(BasePermission):
...
perms_map = {
'GET': [],
'OPTIONS': [],
'HEAD': [],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
...
四、路由配置
1. 路由配置
app的路由配置如下:
python
# 创建api/urls.py
from rest_framework.routers import DefaultRouter
from django.urls import path, include
from api.views import OrderViewSet
router = DefaultRouter()
router.register(r'order', OrderViewSet, basename='order')
urlpatterns = [
path('', include(router.urls))
]
项目的路由配置如下,注意观察我们在最后如何配置jwt
相关的路由:
python
# djangodemo/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path('admin/', admin.site.urls),
]
# app的路由配置
urlpatterns += [
path('api/', include('api.urls'))
]
# jwt获取token的接口配置
urlpatterns += [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
2. simplejwt知识补充
simplejwt
库用于在DRF中快速实现JWT认证,它和django本身的auth
系统也是紧密结合的。只需要简单的配置就能获得两个接口,分别是获取token和刷新token。
我们需要知道以下几点。
- 使用
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>
。
五、编写测试
至此,我们已经顺利完成了JWT授权、RBAC权限校验后端的搭建,现在编写测试来实际运行一下吧:
python
# api/tests.py
from rest_framework.test import APITestCase
from rest_framework.test import APIClient
from rest_framework import status
from django.contrib.auth.models import User
from django.contrib.auth.models import Permission, Group
from django.urls import reverse # reverse可以从basename中解析出正确的路径
class TestAuth(APITestCase):
def setUp(self):
# 创建测试客户端,创建用户和权限组,并将用户分配到权限组上
self.client = APIClient()
self.user = User.objects.create_user(username='用户1', password='test')
self.admin = User.objects.create_superuser(username='admin', password='admin')
group = Group.objects.create(name='test')
self.user.groups.add(group)
def login_user(self):
# 普通用户登录
response = self.client.post(reverse('token_obtain_pair'), {'username': '用户1', 'password': 'test'},
format='json')
if response.status_code == status.HTTP_200_OK:
# 获取令牌配置在请求头上
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + response.data['access'])
def login_admin(self):
# 管理员用户登录
response = self.client.post(reverse('token_obtain_pair'), {'username': 'admin', 'password': 'admin'},
format='json')
if response.status_code == status.HTTP_200_OK:
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + response.data['access'])
def test_order(self):
# 获取所有与order相关的权限,并分配给我们创建的权限组
ps = Permission.objects.filter(codename__icontains='order')
g = Group.objects.get(name='test')
g.permissions.set(ps)
# 用户登录
self.login_user()
# 测试对order的增删改查
rep = self.client.post(
reverse('order-list'),
{'name': '测试订单', 'notes': '用于测试'},
format='json'
)
self.assertEqual(rep.status_code, status.HTTP_201_CREATED)
rep = self.client.get(reverse('order-list'))
self.assertEqual(rep.status_code, status.HTTP_200_OK)
print(rep.data)
总结
Django和DRF开发效率非常高,只需要简单的配置就能魔法般完成大量的工作,回顾我们实现JWT授权的过程,仅仅是在settings.py
中做了一些配置,在urls.py
中注册了两个路由;为视图集设置权限要求时只需要配置一条属性。简单的东西往往最复杂,了解背后的原理才能更加从心所欲。