Django一分钟:DRF快速实现JWT授权以及RBAC权限校验

发布于:2024-10-18 ⋅ 阅读:(11) ⋅ 点赞:(0)

本文将介绍如何使用DRF快速实现JWT授权、RBAC权限校验。此外,文中将会对一些关键配置做讲解,帮助你理解Django、DRF快速开发的背后框架都为我们做了什么。

一、项目创建

本章节将使用包管理工具poetry来快速搭建django项目,如果你不熟悉poetry请自行创建python虚拟环境并下载以下依赖:djangodjangorestframeworkdjangorestframework-simplejwt,相信这并不是什么难事。

  1. 创建项目目录
mkdir djangodemo
cd djangodemo
  1. 初始化并设置国内源
poetry init --no-interaction
poetry source add aliyun https://mirrors.aliyun.com/pypi/simple
  1. 添加依赖
  • django
  • djangorestframework
  • djangorestframework-simplejwt
poetry add django djangorestframework djangorestframework-simplejwt
  1. 创建django项目
poetry run django-admin startproject djangodemo

创建项目时需要启动虚拟环境,poetry run命令可以启动虚拟环境

  1. 调整目录,经过上述步骤所创建的项目目录如下:
/djangodemo
    ├── .venv
    ├── djangodemo # 多余的目录
    │       ├── djangodemo # 项目目录
    │       │       ├── settings.py
    │       │       ├── urls.py
    │       │       ├── wsgi.py
    │       │       └── ...
    │       └── manage.py
    │   
    ├── poetry.lock
    └── pyproject.toml

可以看到django-admin创建的项目目录外,存在一层多余的目录,我们需要做一些调整,把内部真正的项目目录移出来,把外面多余的项目目录删除掉,调整后的项目目录将如下:

/djangodemo
    ├── .venv
    ├── manage.py
    ├── djangodemo  # 项目目录
    │   ├── settings.py
    │   ├── urls.py
    │   ├── wsgi.py
    │   └── ...
    ├── poetry.lock
    └── pyproject.toml
  1. 创建APP,示例中我们创建名为API的APP:
poetry run python manage.py startapp api

如果你使用pycharm建议直接在pycharm的manage.py控制台中进行app创建,pycharm会自动完成settings.py的配置以及其它的一些辅助配置。

  1. 注册drf以及我们创建的app:
# settings.py
INSTALLED_APPS = [
    ...
    "rest_framework",
    "api.apps.ApiConfig",
]

二、模型创建

1. 模型创建

我们创建一个简单的Order(订单)模型,并以User模型为外键,这里的User来自django.contrib.auth.models是django内置的一个用户模型,用于进行授权登录。

# 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 manage.py 
makemigration api
migrate

2. django认证授权系统知识补充

如果你对django的认证和授权系统非常了解请跳过此小节。

在本文中我们将使用Django自带的auth系统来实现RBAC权限校验,在此之前需要了解一些关于Djangoauth系统的基础知识。

完成APP注册后,初次进行migrate数据库迁移的时,除了我们APP中的Order表之外,Django会自动在数据库中创建5张表:用户、权限、组以及三者两两之间的关系表,它们被定义在Django内置的authAPP内。五表设计在RBAC权限管理中非常经典:

  • user
  • group
  • permission
  • user_group
  • group_permission
  • user_permission

使用Django的认证系统我们还需要知道以下几件事:

  1. 可以自己在permission表中创建权限,但通常来说不需要。Django在执行数据库迁移时会自动在permission表中为已注册APP的所有模型创建增、删、改、查四个权限。比如在我们的APP中只有一个Order模型,Django将自动为Order模型在permission表中创建四个权限:add_orderchange_orderdelete_orderview_order
  2. 我们可以为用户分配权限,本质上就是在user_permission关系表中创建一条数据。我们也可以创建一个组,为组分配权限,组中的用户也将持有相同的权限。
  3. 通过createsuperuser 创建的超级用户会拥有所有的权限(准确来说是自动跳过权限认证)。

三、序列化器和视图集

1. 序列化器

Order模型创建序列化器如下:

# 创建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. 视图集

接着使用模型和序列化器创建视图集如下:

# 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的视图集要求我们实现listcreatedestroy等方法,路由会将其自动映射到getpostdelete等处理方法上。

DjangoModelPermissions实际上也是类似的做法,它把不同种类的"权限要求"映射到逻辑上对应的请求处理方法上,如:add_xxxx -> POSTchange_xxx -> PUTdelete_xxx -> DELETE 等等。在DjangoModelPermissions的源码中的perms_map属性清晰的定义了这一点:

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的路由配置如下:

# 创建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相关的路由:

# 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权限校验后端的搭建,现在编写测试来实际运行一下吧:

# 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中注册了两个路由;为视图集设置权限要求时只需要配置一条属性。简单的东西往往最复杂,了解背后的原理才能更加从心所欲。