DRF的使用
1. DRF概述
DRF即django rest framework,是一个基于Django的Web API框架,专门用于构建RESTful API接口。DRF的核心特点包括:
- 序列化:通过序列化工具,DRF能够轻松地将Django模型转换为JSON格式,也可以将JSON格式的数据反序列化为Python对象。
- 认证与权限管理:DRF提供了多种认证方式,如Token认证、OAuth等以及权限控制,确保API的访问是安全的。
- 视图与路由优化:DRF简化了基于类的视图,允许快速构建RESTful API端点,减少重复代码。ViewSet是它的一大特色,使得定义和处理API端点变得更加清晰。并且可以结合Router自动生成URL路由,简化API端点配置。
- 分页与过滤:支持分页、搜索和过滤功能,可以轻松地管理大规模的数据集。
- 强大的文档生成:DRF可以与Swagger等工具集成,自动生成API文档,帮助前端开发者和其他开发人员理解API接口。
总体来说,DRF让Django项目中的API开发更加高效、灵活、且可维护,非常适合构建前后端分离的项目。推荐使用Django 4.2 + DRF 3.15.2进行新项目的开发。包管理工具推荐使用pip,如果涉及机器学习、数据分析等才使用conda,不建议两种混用。原因如下:
- 🐍 官方标准包管理器:Django 官方文档、教程都基于 pip。
- 📦 丰富的第三方库:绝大多数 Django 插件、中间件都通过 PyPI 发布。
- ⚙️ 轻量灵活:适合现代开发流程,如结合 poetry、pipenv 使用。
- 🚀 部署友好:与 requirements.txt 配合良好,便于 CI/CD 和容器化部署。
要在现有的Django 项目中集成DRF,需要安装 djangorestframework 包,如下:
pip install django==4.2 djangorestframework==3.15.2
安装之后在settings.py中注册rest_framework,如下:
INSTALLED_APPS = [ # 其他Django应用 'rest_framework', ]
如果想要修改drf的配置文件,只需要在settings.py中添加配置项REST_FRAMeWORK,如下:
REST_FRAMEWORK = { # 关闭未认证用户的默认行为, 如自定义了身份认证机制[JWT、Token、Oauth2] "UNAUTHENTICATED_USER": None }
2. 用户模型
Django的用户模型是用户认证系统的核心部分,用于表示网站的用户,包括用户名、密码、邮箱、权限等信息。Django默认的用户模型是django.contrib.auth.models.User模型,它继承自django.contrib.auth.models.AbstractUser,代码如下:
class User(AbstractUser): class Meta(AbstractUser.Meta): # 标识这个模型是可以被交换的, 允许开发者通过AUTH_USER_MODEL指定一个自定义用户模型来替代默认的User模型 swappable = "AUTH_USER_MODEL"
2.1 AbstractUser
AbstractUser继承自django.contrib.auth.base_user.AbstractBaseUser,有以下默认字段:该用户模型适合功能不复杂的后台管理系统,且无需扩展用户字段的项目。
字段名称 类型 描述 默认值/约束条件 username CharField 唯一用户名,用于登录系统,允许字母、数字和特定符号 max_length=150,必填且唯一 password CharField 加密存储的密码,使用 PBKDF2 或 Argon2 算法 max_length=128,必填 email EmailField 用户邮箱地址,非必填项 可选,默认空字符串 first_name CharField 用户名字段,用于存储用户的名 max_length=150,可选 last_name CharField 用户姓氏字段,用于存储用户的姓 max_length=150,可选 is_staff BooleanField 标识用户是否有权限访问 Django Admin 后台 默认False is_active BooleanField 标识用户账号是否激活 默认True is_superuser BooleanField 超级管理员标识,拥有所有权限 默认False date_joined DateTimeField 用户注册时间 默认当前时间 【timezone.now】 last_login DateTimeField 用户最后一次登录时间 可为空,初始值为None 2.2 自定义用户模型
由于AbstractUser的局限性,我们可以自定义用户模型,只需要继承AbstractUser 【推荐】或 AbstractBaseUser即可,如下:
# apps/users/models.py from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): # 扩展字段 phone = models.CharField(max_length=11, unique=True, null=True, blank=True) nickname = models.CharField(max_length=20, blank=True, null=True) # 覆盖不想要的AbstractUser模型的字段 first_name = None last_name = None class Meta: db_table = "custom_user" verbose_name = "用户" verbose_name_plural = "用户"
# 指定用户模型为自定义用户模型 AUTH_USER_MODEL = 'users.CustomUser'
操作用户模型的相关API如下:
from django.contrib.auth import get_user_model # 获取当前项目正在使用的用户模型 User = get_user_model() # 创建用户 user = User.objects.create_user(username='aaa', email='aaa@qq.com', password='123456') # 查询用户 user = User.objects.get(email='aaa@qq.com') # 检查密码 user.check_password('123456')
如果是多用户角色,有以下两种常见做法:
-
单一用户模型 + 角色字段:该方法的局限性在于所有用户数据存储在一张表中,且由于不同角色字段的不同,会导致很多空字段。
class User(AbstractUser): ROLE_CHOICES = ( ('student', '学生'), ('teacher', '教师'), ('admin', '管理员'), ) role = models.CharField(max_length=10, choices=ROLE_CHOICES) # 公共字段, 如手机号、头像等 ... # 学生专属字段 ... # 教师专属字段 ...
-
User + OneToOne角色扩展表【推荐】:一个Django项目只能有一个全局的用户模型,因此多用户角色可以使用角色扩展表实现。该设计将不相同的字段存储在用户表中,将通用字段存储在了主表中。
class User(AbstractUser): role = models.CharField(max_length=10, choices=[('student', '学生'), ('teacher', '教师'), ('admin', '管理员')]) class Meta: verbose_name = '用户信息' verbose_name_plural = '用户信息' db_table = 'users' class StudentProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='student_profile') student_id = models.CharField(max_length=20) grade = models.CharField(max_length=10) class TeacherProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='teacher_profile') title = models.CharField(max_length=50) department = models.CharField(max_length=100) class AdminProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='admin_profile') admin_code = models.CharField(max_length=50)
# 创建超级管理员, 用于登录Django后台, 信息保存在users表中 python manage.py createsuperuser
# 创建其它用户角色 class CreateStudentView(APIView): def post(self, request): username = request.data.get('username') password = request.data.get('password') role = request.data.get('role') # 创建学生用户 user = User.objects.create_user(username=username, password=password, role=role) StudentProfile.objects.create(user=user, student_id="S123456", grade="2023") return Response({'message': 'ok'})
# 将用户模型注册到管理后台中 # users/admin.py # Register your models here. from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from .models import User, StudentProfile, TeacherProfile, AdminProfile @admin.register(User) class UserAdmin(BaseUserAdmin): # 控制页面中要展示的字段 list_display = ('username', 'email', 'role', 'is_active', 'is_staff', 'is_superuser') # 添加侧边栏过滤器,快速筛选数据 list_filter = ('role', 'is_active', 'is_staff') # 启用后台搜索框,支持模糊查找,允许通过哪些字段查询 search_fields = ('username', 'email') # 控制用户编辑页中字段的分组和顺序 fieldsets = BaseUserAdmin.fieldsets + ( ('角色信息', {'fields': ('role',)}), ) @admin.register(StudentProfile) class StudentProfileAdmin(admin.ModelAdmin): list_display = ('user', 'student_id', 'grade') search_fields = ('student_id', 'user__username') @admin.register(TeacherProfile) class TeacherProfileAdmin(admin.ModelAdmin): list_display = ('user', 'title', 'department') # 注意双下划线 search_fields = ('user__username', 'department') @admin.register(AdminProfile) class AdminProfileAdmin(admin.ModelAdmin): list_display = ('user', 'admin_code') search_fields = ('user__username', 'admin_code')
3. 认证后端
认证后端是Django中处理登录身份验证的核心机制,用来判断账户提供的用户名和密码【或其它凭证】是否合法,并返回合法的用户对象User或None。当调用authenticate方法时会触发认证后端,如下:
from django.contrib.auth import authenticate, login user = authenticate(request, username="admin", password="123456") # 返回成功后可以保存session from django.contrib.auth import login login(request, user) # 当然也可以销毁session logout(request)
Django会遍历AUTHENTICATION_BACKENDS 中配置的每一个认证后端,按顺序执行它们的authenticate方法,直到有一个返回User对象,否则返回None。
Django中默认的认证后端是django.contrib.auth.backends.ModelBackend,它的行为是查找用户username,比对密码password。如果想要实现邮箱登录或其他登录,则需要自定义认证后端:
# apps/users/backends.py from django.contrib.auth.backends import ModelBackend from django.contrib.auth import get_user_model User = get_user_model() class EmailBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): try: user = User.objects.get(email=username) # username 实际是 email if user.check_password(password): return user except User.DoesNotExist: return None
# settings.py AUTHENTICATION_BACKENDS = [ 'apps.users.backends.EmailBackend', ]
# 在登录视图中调用 @api_view(['POST']) def login_view(request): email = request.data.get('email') password = request.data.get('password') # 仍然传入username=email user = authenticate(request, username=email, password=password) if user: login(request, user) return Response({'msg': '登录成功'}) return Response({'msg': '登录失败'}, status=401)
认证后端与认证组件的区别与联系如下:
项目 认证后端 认证组件 属于 Django核心 DRF 什么时候用 登录视图中调用authenticate 每个API请求识别用户 作用 识别账号 + 密码的是否合法 识别这个请求是谁发的 输入 用户名/密码或其它凭证 token、cookie、JWT 举例 邮箱登录、自定义账号逻辑 自定义 token、cookie、JWT 是否每次请求执行 ❌ 只在登录时用一次 ✅ 每个受保护 API 请求都会调用 是否与authenticate有关 是 无关 它们的配合流程如下:
[ 用户请求登录 ] ↓ [ 调用 authenticate(username=xxx, password=xxx) ] ↓ [ 遍历 Authentication Backend → 返回 user ] ↓ [ 调用 login(request, user) → 设置 session或token ] ========================= [ 之后每个 API 请求 ] ↓ [ 调用 Authentication Class → 从 token/session 中识别用户 ] ↓ [ 返回 (user, auth) → 自动赋值 request.user ]
✅ 正确理解整个 Token 登录流程:
阶段 你应该做的事 使用什么 登录视图中 ✅ 调用 authenticate(username=..., password=...) 验证账号密码 调用 Django 的认证后端(AUTHENTICATION_BACKENDS) 登录成功后 ✅ 创建或获取 token 使用 Token.objects.get_or_create(user=user) 返回给前端 ✅ 返回 {"token": "xxx"} 用 JsonResponse / Response 返回即可 后续请求验证 ❌ 不用你手动做,DRF 自动用 TokenAuthentication 来解析请求 它会从请求头中找 token,并识别用户 4. 认证组件
在DRF中,认证组件用于验证用户的身份【你是谁】,判断用户是否登录,确保只有经过授权的用户才能访问特定的API视图。DRF提供了多种认证类,每种认证类都实现了特定的认证逻辑。常见的认证类包括:
- BasicAuthentication:基于HTTP基本认证,通过用户名和密码进行认证。
- SessionAuthentication:基于Django的会话机制进行认证,适用于基于浏览器的交互。
- TokenAuthentication:基于令牌Token进行认证,适用于前后端分离的应用。
- JSONWebTokenAuthentication:需要安装 djangorestframework-jwt 库,基于JSON Web Token即JWT进行认证,是一种无状态的认证方式。
- Custom Authentication:自定义认证类,有时内置的认证类无法满足特定需求,这时可以自定义认证类。DRF允许我们通过继承 BaseAuthentication 类来实现自己的认证逻辑。
认证组件必须配合权限组件一起使用,不然认证就没有意义,因为默认权限是AllowAny,表示所有人都可以访问。就好比你进入一栋写字楼,authentication是你出示身份证,确认你是谁?permission是保安根据规则判断你能不能进入大门。如果你只配置了认证,但没有规定保安不让陌生人进,那默认每个人都可以进 —— 所以你访问不需要登录也能成功。
4.1 BasicAuthentication
BasicAuthentication 是一种简单的 HTTP 认证方式,通常用于 REST API 接口,在开发和测试环境中使用频繁,它也是Django中默认的认证组件,需配合isAuthenticated使用。客户端发送请求时,如果没有认证头时则返回401 Unauthorized响应,并且包含www-Authenticate头。当包含认证头时,则会解析认证头,提取用户民和密码,并验证其合法性。BasicAuthentication的优缺点如下:
-
优点:简单易用,兼容性好,不依赖Cookie和Session,适用于无状态的API服务。适合用于开发和测试环境,但是不能用于生产环境。
-
缺点:安全性低,用户名及密码暴露在认证头中;无会话机制,每次请求都要携带账号密码,效率低且不适合长期登录;不支持登出机制,除非客户端不再发送请求头;用户体验差,需要频繁的输入用户名和密码等。
REST_FRAMEWORK = { # 'UNAUTHENTICATED_USERNAME': None 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.BasicAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), }
class HelloView(APIView): def get(self, request): return Response({"message": f"Hello, {request.user.username}!"})
4.2 SessionAuthentication
SessionAuthentication 是基于 Django 自带的会话Session机制的认证方式。它的原理是:
-
用户通过 Django 的登录视图登录成功,Django 创建一个 Session,服务器保存 session 数据,同时发给客户端一个 sessionid 。
-
客户端在后续请求中自动携带 sessionid,DRF 的 SessionAuthentication 会从这个 session 中获取用户身份。
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.SessionAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], }
# 自定义登录视图 def custom_login_view(request): if request.method == "POST": try: data = json.loads(request.body) username = data.get("username") password = data.get("password") except Exception: return JsonResponse({"error": "Invalid input"}, status=400) user = authenticate(request, username=username, password=password) if user: login(request, user) # 使用Django的session login return JsonResponse({"message": "Login success", "username": user.username}) else: return JsonResponse({"error": "Invalid credentials"}, status=401) return JsonResponse({"error": "Only POST allowed"}, status=405)
登录成功后会返会Session ID,在浏览器中,后续请求会自动携带Session ID。需要注意的是,SessionAuthentication需要依赖Cookie和CSRF,不适合前后端分离项目,有状态认证机制【需在服务端存储Session】。
SessionAuthentication支持登出,如下:
from django.contrib.auth import logout from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @api_view(['POST']) @permission_classes([IsAuthenticated]) def logout_view(request): logout(request) # 销毁 session return Response({'message': 'Logout successful'})
4.3 TokenAuthentication
TokenAuthentication是一种无状态的认证机制,其核心原理是用户登录后,服务端返回一个Token给客户端,客户端之后访问所有接口时都在请求头携带这个Token【Authorization: Token 】,服务端通过它识别用户身份,返回给前端的token也会保存在authen_token数据表中,后续认证也就是查看数据表中是否有携带的token。其使用如下:
INSTALLED_APPS = [ ... 'rest_framework', 'rest_framework.authtoken', ] # 迁移数据库创建token表 python manage.py migrate REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ] }
from rest_framework.authtoken.views import obtain_auth_token urlpatterns = [ # 内置的登录视图只支持 username + password # 通过认证后端的用户会返回一个token path('login/', obtain_auth_token), ]
值得注意的是,使用Djano的TokenAuthentication只接受如下的Header,这也就意味着在postman中只能手动添加请求头:并且浏览器是不会自动携带Token的。
Authorization: Bearer
Django的TokenAuthentication虽然使用很方便,但是有着很多局限性和缺点,因为生产环境中并不推荐使用TokenAuthentication。
-
Token永不过期:一旦泄露,别人可永久使用,除非手动删除。
-
不支持刷新:无法像JWT刷新token,有效期无法动态延长。
-
不支持多端登录:默认用户只有一个token,不能区分Web、手机或其他设备。
-
安全性低:Token是明文存储在数据库中的,盗用风险高,依赖HTTPS保证安全。
-
无签名校验:服务端要查数据库,效率低且无法校验 token 真伪。
-
不支持登出:没有标准的注销机制,只能手动删除 token 或换一个。
-
不易扩展:不支持加自定义字段、角色、权限等高级内容。
4.4 JSONWebTokenAuthentication
JSONWebTokenAuthentication即JWT认证,是一种无状态的、签名验证的令牌认证机制,服务端签发token后,不再保存用户登录状态,客户端每次请求时携带token,服务端验证签名即可。在前后端分离、移动端或小程序场景中,JWT 是非常主流的认证方式。
pip install djangorestframework-simplejwt
INSTALLED_APPS = [ ... 'rest_framework', 'rest_framework_simplejwt', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ] } from datetime import timedelta SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), # 每次刷新AccessToken时是否刷新RefreshToken 'ROTATE_REFRESH_TOKENS': True, # 是否将旧Token加入黑名单,配合ROTATE_REFRESH_TOKENS=True使用 'BLACKLIST_AFTER_ROTATION': True, # 自定义盐, 默认使用django的盐 'SIGNING_KEY': 'my-very-secret-salt-used-to-sign-tokens-123456', }
from django.urls import path from rest_framework_simplejwt.views import ( TokenObtainPairView, # 登录 TokenRefreshView, # 刷新 token TokenVerifyView # 校验 token 是否有效 ) urlpatterns = [ # 通过认证后端时才返回token path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), # 登录 path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # 刷新 path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'), # 验证 ]
4.5 Custom Authentication
首先是需要清楚为什么需要自定义认证组件,对于上述的四种认证组件,如果对它们的默认行为不满意,比如token在请求体中、实现多端控制登录、支持企业微信、微信小程序登录等等,这时就需要使用自定义认证组件才能完成需求。
对于自定义的认证类,需要继承BaseAuthentication 类并重写authenticate方法,其中authenticate方法在认证成功时必须有返回值,其返回值为(requset.user, request.auth):
- user:表示经过认证的用户对象,通常是 User 模型的实例,或者其他表示用户身份的对象。
- auth:表示与认证相关的附加信息,通常是 None,除非你需要传递其他信息,如Token、API Key等认证数据。
对于BaseAuthentication,有以下两个方法:
-
authenticate:执行具体的认证逻辑,验证请求的合法性。若认证成功则返回(user, auth),返回None则表示允许其它认证类继续尝试,抛出异常则明确表示认证失败,终止后续认证流程。
-
authenticate_header:用于在认证失败时,为客户端返回一个 WWW - Authenticate 响应头,响应状态码为401,用于指导客户端如何重新认证。如果不重写此方法,认证失败时默认返回**403 Forbidden** 而非401 Unauthorized,可能导致前端无法正确处理认证流程。
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed class MyCustomAuthentication(BaseAuthentication): def authenticate(self, request): # 模拟认证失败 raise AuthenticationFailed('Invalid authentication credentials.') def authenticate_header(self, request): # 返回 WWW - Authenticate 头信息 return 'Bearer realm="api"'
HTTP/1.1 401 Unauthorized WWW - Authenticate: MyCustomAuth realm="api" Content - Type: application/json { "detail": "Invalid authentication credentials." }
下面是【局部认证】的简单使用,一般使用CBV方式为某些视图指定认证类:
from rest_framework.authentication import BaseAuthentication from django.contrib.auth import get_user_model from rest_framework.exceptions import AuthenticationFailed User = get_user_model() class CustomHeaderTokenAuthentication(BaseAuthentication): def authenticate(self, request): token = request.META.get("HTTP_X_AUTH_TOKEN") # 自定义header if not token: return None # 返回 None 表示此认证组件不处理该请求 try: user = User.objects.get(auth_token=token) except User.DoesNotExist: raise AuthenticationFailed("Invalid token") return (user, token)
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'yourapp.auth.CustomHeaderTokenAuthentication', ] }
如果不使用【全局认证】,就需要单独给每个视图单独指定认证类,难以管理。下面是全局认证的简单使用:
REST_FRAMEWORK = { "UNAUTHENTICATED_USER": None, "DEFAULT_AUTHENTICATION_CLASSES": [ "api.views.MyAuthentication", ], }
如果使用全局认证,认证类就一定不能写在视图文件中了,其原因就是出现了循环引用。如果同时使用了全局认证和局部认证,则使用局部认证,本质上就是一个覆盖的问题。
4.6 DRF中的request
dispatch 方法是视图处理请求的核心调度器,它负责接收请求,进行一系列的初始化、权限检查、异常处理等操作,最后将响应返回给客户端。其源代码如下:
def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ self.args = args self.kwargs = kwargs request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # Get the appropriate handler method if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) except Exception as exc: response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
其中request = self.initialize_request(request, *args, **kwargs)是对原始的Django请求对象进行封装,将其转换为 DRF 的Request对象,该对象包含了更多与RESTful API相关的属性和方法,例如请求的认证信息、解析后的请求数据等。drf中的request是对原始请求对象的封装,下面是几个常用的扩展方法:
- request.query_params:用于获取URL中的查询参数,等同于Django原生 HttpRequest 对象的 request.GET。
- request.user:用于获取经过认证的用户对象。如果请求经过了认证,request.user 将是一个有效的用户实例,如果未认证,默认是 AnonymousUser。
- request.auth:用于获取认证后得到的认证信息,例如令牌Token。具体的值取决于使用的认证类。
- request.is_authenticated:检查请求是否经过了认证。
4.7 多个认证类
在DRF中,多个认证类的队列是非常重要的。DRF会按照在 settings.py 中配置的顺序依次尝试每个认证类,直到某个认证类成功认证为止,或者所有认证类都失败为止。每个认证类的 authenticate 方法会返回不同的结果,DRF 会根据这些结果进行相应处理:
- 认证成功:如果某个认证类的 authenticate 方法返回一个包含用户对象和认证信息的元组 (user, auth),则认为认证成功。此时,DRF会停止继续尝试后续的认证类,并将认证成功的用户对象和认证信息存储在 request.user 和 request.auth 中,供后续的权限检查和视图处理使用。
- 无法认证:如果某个认证类的 authenticate 方法返回 None,表示该认证类无法对当前请求进行认证。DRF会忽略这个结果,并继续尝试下一个认证类。
- 认证失败:如果某个认证类的 authenticate 方法抛出 AuthenticationFailed 异常,则表示认证失败。DRF 会立即停止认证过程,并将该异常传递给异常处理机制,最终返回一个包含错误信息的响应给客户端,通常状态码为 401未授权。
如果所有配置的认证类的 authenticate 方法最终都返回 None,即没有任何一个认证类能够成功认证请求,这时候就会涉及到是否认证失败的判断,而这取决于权限类的配置:
-
权限类允许未认证访问:若权限类允许未认证的用户访问视图,例如使用 AllowAny 权限类,即使所有认证类都返回 None,请求也不会被判定为认证失败,而是可以正常访问视图。示例代码如下:
from rest_framework.views import APIView from rest_framework.permissions import AllowAny from rest_framework.response import Response class MyView(APIView): authentication_classes = [MyAuthentication, MyAuthentication2] permission_classes = [AllowAny] def get(self, request): return Response({'message': 'This view allows unauthenticated access.'})
-
权限类要求认证:如果权限类要求用户必须经过认证才能访问视图,例如使用 IsAuthenticated 权限类,当所有认证类都返回None 时,就会被判定为认证失败。DRF会抛出 AuthenticationFailed 异常,并返回一个状态码为401的错误响应给客户端。示例代码如下:
from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response class MyProtectedView(APIView): authentication_classes = [MyAuthentication, MyAuthentication2] permission_classes = [IsAuthenticated] def get(self, request): return Response({'message': 'This is a protected view.'})
总而言之,当返回(user, auth)时表示认证成功,如果所有认证类均返回None则表示认证未通过但不是认证失败,将返回匿名用户(AnonymousUser, None),如果不想匿名用户的user为Anonymous,可以通过设置'UNAUTHENTICATED_USER': None。当用户端提供了无效的凭证,在认证组件中会明确的判断为认证失败即抛出AuthenticationFailed异常,DRF 会立即停止遍历其他认证组件。
5. 权限组件
在DRF中,权限组件用于控制对API视图的访问,确保只有满足特定条件的用户才能访问相应的资源。DRF提供了多个内置的权限类,如下:
- AllowAny:允许任何用户访问,不管用户是否经过认证。使用该组件时不需要配置认证组件。
- IsAuthenticated:只允许经过认证的用户访问。
- IsAdminUser:只允许管理员用户,即 is_staff 为 True 的用户访问。
- IsAuthenticatedOrReadOnly:允许经过认证的用户进行任何操作,对于未认证的用户只允许进行只读操作,如 GET 请求。
- 自定义权限组件:如果内置的权限类无法满足业务需求,你可以自定义权限类。自定义权限类需要继承 BasePermission 类,并实现 has_permission 或 has_object_permission 方法。
对于权限组件,其配置方式和认证组件的配置方式一致。权限组件有以下注意点:
-
权限类的顺序:当配置多个权限类时,DRF会按照它们在列表中的顺序依次检查权限。如果前面的权限类检查不通过,后面的权限类将不再检查。因此,需要合理安排权限类的顺序,通常将最宽松的权限类放在前面,最严格的放在后面。
-
与认证组件协同工作:权限组件依赖于认证组件,只有在认证成功后才能进行权限检查。因此,需要确保在使用权限组件之前已经正确配置了认证组件。如果认证失败,权限检查将无法正常进行。
-
异常处理:当权限检查不通过时,DRF会抛出 PermissionDenied 异常,并返回一个状态码为403禁止访问的响应。可以通过自定义异常处理函数来改变默认的错误响应,以满足特定的业务需求。
from rest_framework.views import exception_handler from rest_framework.response import Response def custom_exception_handler(exc, context): response = exception_handler(exc, context) if response is not None and isinstance(exc, rest_framework.exceptions.PermissionDenied): response.data = {'error': 'You do not have permission to access this resource.'} return response
REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'your_app.utils.custom_exception_handler' }
对于自定义的权限组件,BasePermission中的has_permission和has_object_permission默认返回True,表示权限通过,反之不通过。这两个方法有以下区别:
-
has_permission:决定是否允许整个视图类的访问。
- 调用时机:在调用视图的get()、post()方法之前执行。它主要针对视图级别的权限控制,不涉及具体的对象实例。例如,当客户端发起一个请求访问某个视图时,DRF 会首先调用视图所配置的权限类的 has_permission 方法来决定是否允许该用户继续访问这个视图。
- 应用场景:适用于对整个视图的访问进行权限控制,比如限制只有认证用户才能访问某个视图,或者只有特定角色的用户才能访问某个视图集。例如一个博客系统的文章列表视图,可能只允许已登录用户访问,就可以在 has_permission 方法中进行判断。
-
has_object_permission:针对单个对象如详情页、更新或删除操作的权限验证。
-
调用时机:该方法在视图处理涉及具体对象的操作时被调用,前提是 has_permission 方法已经返回 True,即用户已经通过了视图级别的权限检查。has_object_permission 用于判断用户是否有权限对特定的对象实例进行操作,如对某个数据库记录进行更新或删除。
-
应用场景:适用于对具体对象的操作进行权限控制,例如只有文章的作者才能对文章进行修改或删除操作。在这种情况下,需要根据具体的文章对象来判断用户是否有权限进行操作,就可以使用 has_object_permission 方法。
class BasePermission(metaclass=BasePermissionMetaclass): """ A base class from which all permission classes should inherit. """ # 权限拒绝时的提示信息 message = {"code": 1001, "msg": "无权访问"} def has_permission(self, request, view): """ Return `True` if permission is granted, `False` otherwise. """ return True def has_object_permission(self, request, view, obj): """ Return `True` if permission is granted, `False` otherwise. """ return True
上述两个方法中有两个参数,view和obj,它们都是由django自动传入到你的权限类方法中的,使用如下:
-
view:当前请求的视图实例,可以访问视图的 queryset、serializer_class、action,以及视图类中的自定义属性。
def has_permission(self, request, view): # 若视图类中定义了scope属性 if view.scope == 'admin': return request.user.is_staff return True
-
obj:表示当前操作的具体对象的模型。
def has_object_permission(self, request, view, obj): # 允许读操作,写操作需验证所有者 if request.method in permissions.SAFE_METHODS: return True return obj.owner == request.user
5.1 单个权限类
from rest_framework import permissions class IsAuthorOrAdminOrReadOnly(permissions.BasePermission): """ 允许任何人进行只读操作 只允许文章作者或管理员进行写操作 """ def has_permission(self, request, view): """ 负责视图级别的权限检查 """ # SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') if request.method in permissions.SAFE_METHODS: return True # 如果是写操作如POST则要求用户必须登录 return request.user and request.user.is_authenticated def has_object_permission(self, request, view, obj): """ 负责对象级别的权限检查 """ if request.method in permissions.SAFE_METHODS: return True # 如果是写操作如PUT、PATCH、DELETE则检查当前用户是否是该文章的作者或者是否是管理员 return obj.author == request.user or request.user.is_staff
from rest_framework import viewsets from .models import Post from .serializers import PostSerializer from .permissions import IsAuthorOrAdminOrReadOnly # 导入自定义权限类 class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer # 在这里应用我们的权限类 authentication_classes = [SessionAuthentication, TokenAuthentication] permission_classes = [IsAuthorOrAdminOrReadOnly] def perform_create(self, serializer): # 在创建文章时,自动将作者设置为当前登录用户 serializer.save(author=self.request.user)
5.2 多个权限类
使用方式与单个权限类一致,需要注意的是当某个权限类未通过,之后的权限类将不再执行。但是在实际开发中,有时并不需要所有权限类都通过才能访问对应的资源,符合某一个条件就可以访问该资源。为了实现这一需求,可以使用如下:
from rest_framework.permissions import IsAuthenticated, IsAdminUser from rest_framework.combinators import Or # 满足任一权限即可 permission_classes = [Or(IsAuthenticated, IsAdminUser)]
6. 限流组件
在DRF 中,限流是一个非常有用的功能,能够限制某个用户在一定时间内能发送多少次请求。限流通常用于防止恶意攻击、避免服务过载、保护 API 资源,或者简单地控制流量。限流通常是通过对请求进行计数,并且每个用户或 IP 在一定时间内只能发起有限次数的请求来实现的,例如每个用户每分钟最多能发起100个请求。DRF提供了几种常用的限流类,我们可以在 全局配置或 视图级别设置限流。
-
AnonRateThrottle:AnonRateThrottle 用于限制未认证的用户的请求次数。
-
UserRateThrottle:UserRateThrottle 用于限制已认证的用户的请求次数。
# settings.py REST_FRAMEWORK = { # 限流类 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.UserRateThrottle', # 针对认证用户的限流 'rest_framework.throttling.AnonRateThrottle', # 针对匿名用户的限流 ], # 限流速率 'DEFAULT_THROTTLE_RATES': { 'user': '100/hour', # 认证用户每小时最多 100 次请求 'anon': '10/minute', # 匿名用户每分钟最多 10 次请求 }, }
-
ScopedRateThrottle:针对不同视图或端点的限流。ScopedRateThrottle 可以为不同的视图或视图集设置不同的限流策略。
class ContactListView(APIView): throttle_scope = 'contacts' # 关联限流作用域 ... # settings.py 配置 REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES': {'contacts': '50/day'} }
-
CustomThrottle:如果内置的限流类不满足需求,DRF允许自定义限流类。你可以继承 BaseThrottle 类来实现自定义的限流逻辑。对于BaseThrottle类,里面限流的逻辑都需要自己实现,较为麻烦。这时可以用到BaseThrottle类的子类SimpleRateThrottle。
from rest_framework.throttling import SimpleRateThrottle from django.core.cache import cache as default_cache class MyThrottle(SimpleRateThrottle): scope = 'user' # 配置节流 THROTTLE_RATES = {scope: '3/m'} cache = default_cache def get_cache_key(self, request, view): if request.user: ident = request.user.pk else: # 获取用户的IP地址 ident = self.get_ident(request) return self.cache_format % { 'scope': self.scope, 'ident': ident }
DRF 限流依赖 Django 缓存系统,默认使用 LocMemCache,若需使用其他缓存如 Redis,需在 settings.py 中配置。
# settings.py CACHES = { "default": { # 限流默认使用此配置 "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/0", # 使用 Redis 数据库 0 }, "rate_limit_cache": { # 其他缓存配置 "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", # 使用 Redis 数据库 1 } }
from rest_framework.throttling import SimpleRateThrottle from django.core.cache import caches # 导入多缓存管理器 class CustomRedisThrottle(SimpleRateThrottle): scope = "custom" # 限流作用域 cache = caches["rate_limit_cache"] rate = "100/day" # 限流频率 def get_cache_key(self, request, view): # 自定义逻辑生成唯一标识(如用户 ID 或 IP) return f"custom_{request.user.id if request.user else self.get_ident(request)}"
7. 版本控制
在 DRF里,版本控制是一项重要的功能,它允许你在同一个 API 端点上提供不同版本的服务,以满足不同客户端的需求,同时确保系统的兼容性和可维护性。版本控制的作用就是随着业务的发展,API 可能会发生变化。通过版本控制,旧版本的客户端可以继续使用旧版本的 API,而新版本的客户端可以使用新功能。DRF 提供了多种版本控制的实现方式,下面分别介绍:
-
基于查询参数QueryParameterVersion:通过在 URL 的查询参数中指定版本号,例如 http://example.com/api/resource/?version=v1。
from rest_framework.versioning import QueryParameterVersioning class OrderView(APIView): authentication_classes = [] versioning_class = QueryParameterVersioning def get(self, request): print(request.version) return Response('订单列表')
# 控制版本的名称 REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', 'ALLOWED_VERSIONS': ['v1', 'v2'], 'VERSION_PARAM': 'version' }
-
基于URL的版本控制URLPathVersioning:在 URL 路径中包含版本号,例如 http://example.com/api/v1/resource/。
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning' }
from django.urls import path from rest_framework.views import APIView from rest_framework.response import Response class MyView(APIView): def get(self, request, version): return Response({'version': version}) urlpatterns = [ path('api//resource/', MyView.as_view()), ]
-
基于请求头AcceptHeaderVersioning:在请求头的 Accept 字段中指定版本号,例如 Accept: application/json; version=v1。
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning' }
from rest_framework.views import APIView from rest_framework.response import Response class MyView(APIView): def get(self, request): version = request.version return Response({'version': version})
在 DRF的版本控制机制里,version 和 versioning_scheme 是两个关键的概念,下面为你详细介绍它们的含义以及 versioning_scheme 的使用方法。
-
version :当前请求所使用的 API 版本号。在处理请求时,DRF 会根据所配置的版本控制方案从请求中提取版本信息,并将其存储在 request.version 属性中,方便在视图里获取和使用该版本号,从而依据不同的版本返回不同的响应。
class UserView(APIView): def get(self, request): if request.version == 'v1': return Response({"name": "Alice"}) elif request.version == 'v2': return Response({"name": "Alice", "email": "alice@example.com"})
-
versioning_scheme :负责实现版本解析逻辑和反向生成 URL,反向生成url如下:
class OrderView(APIView): authentication_classes = [] versioning_class = QueryParameterVersioning def get(self, request): url = request.versioning_scheme.reverse('order-list', request=request) print(url)
8. 解析器
在DRF中,解析器Parsers用于将传入的请求数据解析为 Python 对象,以便在视图中进行处理。DRF 提供了多种内置的解析器,其中下面的前三章解析器是drf默认的解析器:解析器仅用于格式转换,不执行任何数据验证。
-
JSONParser:用于解析JSON格式的数据。这是最常用的解析器之一,适用于处理以 JSON 格式发送的 API 请求,例如前端通过AJAX发送JSON数据到后端。
-
FormParser:用于解析表单数据,通常用于处理 application/x-www-form-urlencoded 格式的数据,这种格式是传统的 HTML 表单提交数据的方式。
-
MultiPartParser:用于处理多部分表单数据,特别是包含文件上传的情况,格式为 multipart/form-data。当用户通过表单上传文件时,就需要使用这个解析器。
-
自定义解析器:如果内置的解析器无法满足项目需求,还可以自定义解析器。自定义解析器需要继承 BaseParser 类,并实现 parse 方法。开发中使用默认的解析器足够使用。
# 全局配置 REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': [ 'rest_framework.parsers.JSONParser', # 解析 JSON 数据 'rest_framework.parsers.FormParser', # 解析表单数据x-www-form-urlencoded 'rest_framework.parsers.MultiPartParser' # 解析文件上传multipart/form-data ] }
from rest_framework.versioning import QueryParameterVersioning class OrderView(APIView): parser_classes = [JSONParser, FormParser] def get(self, request): # 访问解析后的数据 print(request.data)
9. 序列化器
在DRF中,序列化Serialization是一个非常重要的概念,它主要用于将复杂的数据类型如 Django 模型实例、查询集转换为Python原生数据类型,以便可以轻松地将其渲染为 JSON、XML 等格式的响应数据。同时,也能将传入的原始数据反序列化为 Django 模型实例或其他复杂数据类型,方便进行数据验证和保存到数据库。
如果没有序列化器,通过model.Depart.objects.all().first()获得的模型实例是一个复杂的python对象,又或是model.Depart.objects.all()返回的一个QuerySet对象,是不能直接通过Response转换为JSON格式的,应该将其转换为python中的原生对象再传入Response,这时就需要用到序列化器。
对于创建序列化器实例时的参数问题,如下:
- 序列化:传入instance参数,如ArticleSerializer(instance=article),通常发生在Get请求中。
- 反序列化:传入data参数,如ArticleSerializer(data=article),通常发生在Post、Put、Patch请求中。通常会涉及serializer.save() 方法的使用,用于修改或保存数据,对于参数方面也有以下两种使用方式:
- serializer = MySerializer(data=request.data):用于创建,会触发create方法。
- serializer = MySerializer(instance=obj, data=request.data):用于更新,会触发update方法。
9.1 Serializer
Serializer:这是最基础的序列化器,需要手动定义每个字段。适用于需要自定义序列化和反序列化逻辑的复杂场景,如当你的数据源不涉及 Django 模型、需要高度定制序列化行为即默认行为create、update不满足需求时才使用。
# reports/serializers.py from rest_framework import serializers from datetime import datetime, timedelta class EventSerializer(serializers.Serializer): # 字段定义 ----------------------------------------------------------------------------------------------- # 不涉及django模型时,序列化器字段必须与处理的数据字典的键名一致才能正确获取到值,因为Serializer不具有自动推断的功能 ----------------------------------------------------------------------------------------------- event_id = serializers.UUIDField(format='hex_verbose', read_only=True) # UUID格式 event_type = serializers.CharField(max_length=50) source_system = serializers.CharField(max_length=100, required=False, allow_blank=True, default="UNKNOWN") payload = serializers.JSONField() # 任意JSON数据 timestamp = serializers.DateTimeField(read_only=True) # 表示处理时间 # --- 字段级别验证示例 --- def validate_event_type(self, value): """ 验证事件类型是否为预定义类型之一 """ allowed_types = ['login', 'logout', 'purchase', 'error', 'page_view'] if value.lower() not in allowed_types: raise serializers.ValidationError( f"无效的事件类型 允许的类型是: {', '.join(allowed_types)}" ) return value # --- 对象级别验证示例 --- def validate(self, data): """ 验证特定事件类型下的数据规则。 例如,如果是 'purchase' 事件,payload 必须包含 'amount' 字段。 """ if data['event_type'].lower() == 'purchase': if 'amount' not in data['payload'] or not isinstance(data['payload']['amount'], (int, float)): raise serializers.ValidationError( {"payload": "Purchase事件的payload必须包含有效的'amount'字段"} ) if data['payload']['amount'] "payload": "Purchase事件的'amount'必须大于零"} ) return data # --- create 方法示例 反序列化时使用 --- def create(self, validated_data): # 模拟生成一个事件ID validated_data['event_id'] = f"evt-{datetime.now().timestamp()}" validated_data['timestamp'] = datetime.now() # 模拟将事件数据发送到日志系统或消息队列 print(f"--- 接收到并处理了新事件 ---") print(f" Event Type: {validated_data['event_type']}") print(f" Source: {validated_data['source_system']}") print(f" Payload: {validated_data['payload']}") print(f" Processed At: {validated_data['timestamp']}") print(f"---------------------------\n") # 返回处理后的数据,通常包含新生成的ID和时间戳 return validated_data # --- update 方法示例 反序列化时使用 --- def update(self, instance, validated_data): """ 对于像事件日志这样的数据,通常是只创建不更新 所以这里不实现 update 方法,或者直接抛出 NotImplementedError 如果需要更新,例如更新一个临时事件的状态,你可以修改这里 """ # 简单模拟更新 instance 的部分字段 instance['event_type'] = validated_data.get('event_type', instance['event_type']) instance['source_system'] = validated_data.get('source_system', instance['source_system']) instance['payload'] = validated_data.get('payload', instance['payload']) instance['timestamp'] = datetime.now() # 更新时间戳 print(f"--- 更新了事件 ---") print(f" Event ID: {instance.get('event_id', 'N/A')}") print(f" New Type: {instance['event_type']}") print(f" Updated At: {instance['timestamp']}") print(f"-------------------\n") return instance "event_id": uuid.UUID("a1b2c3d4-e5f6-7890-1234-567890abcdef"), "event_type": "login", "source_system": "web_app", "payload": {"user_id": 101, "ip_address": "192.168.1.1"}, "timestamp": datetime.now() - timedelta(hours=2) }, { "event_id": uuid.UUID("f0e9d8c7-b6a5-4321-fedc-ba9876543210"), "event_type": "page_view", "source_system": "mobile_app", "payload": {"page_url": "/dashboard", "user_id": 102}, "timestamp": datetime.now() - timedelta(minutes=30) } ] class EventListAPIView(APIView): permission_classes = [AllowAny] def get(self, request, format=None): """ 序列化操作:将Python列表转换为可供API响应的格式 """ serializer = EventSerializer(instance=_mock_events_data, many=True) # serializer.data 包含序列化后的 Python 字典或列表 print("GET Request - Serialized data:", serializer.data) # 如{'id': 1, 'name': 'Alice', 'age': 30, 'email': 'alice@example.com'} return Response(serializer.data, status=status.HTTP_200_OK) def post(self, request, format=None): """ 反序列化操作:接收客户端数据,进行验证,并模拟处理 """ # 实例化序列化器,传入客户端发送的数据 serializer = EventSerializer(data=request.data) # 调用is_valid()进行数据验证 if serializer.is_valid(): # serializer.validated_data包含了验证通过并转换后的Python字典 print("POST Request - Validated data type:", type(serializer.validated_data)) print("POST Request - Validated data:", serializer.validated_data) processed_event = serializer.save() # 将处理后的数据作为响应返回 return Response(processed_event, status=status.HTTP_201_CREATED) else: # 如果验证失败,serializer.errors 包含错误信息 print("POST Request - Validation Errors:", serializer.errors) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class EventDetailAPIView(APIView): permission_classes = [AllowAny] def get_object(self, event_id): # 模拟从 _mock_events_data 中查找一个事件 for event in _mock_events_data: print("GET Request - Event ID:", event['event_id']) if event['event_id'] == event_id: return event raise status.HTTP_404_NOT_FOUND def get(self, request, event_id, format=None): print("GET Request - Event IDaaaaa:", event_id) """ 单个对象的序列化 """ try: event = self.get_object(event_id) except Exception: return Response({"detail": "Event not found."}, status=status.HTTP_404_NOT_FOUND) serializer = EventSerializer(event) # 传入单个Python字典进行序列化 return Response(serializer.data, status=status.HTTP_200_OK) def put(self, request, event_id, format=None): """ 单个对象的反序列化 更新操作 """ try: event = self.get_object(event_id) except Exception: return Response({"detail": "Event not found."}, status=status.HTTP_404_NOT_FOUND) # 实例化序列化器,传入现有实例 event 和新数据request.data # 传入一个instance调用的就是update而不是create serializer = EventSerializer(instance=event, data=request.data, partial=True) # partial=True 允许部分更新 if serializer.is_valid(): # 调用 serializer.save(), 会触发 EventSerializer的update()方法 updated_event = serializer.save() return Response(updated_event, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) "id": 1, "title": "My Article", "categories": [1, 2] // 关联的Tag对象的ID列表 } 'id': author.id, 'name': author.name } def get_category_info(self, obj): categories = obj.categories.all() return [ { 'id': category.id, 'name': category.name } for category in categories ] class Meta: model = Book fields = ['id', 'title', 'author_info', 'category_info'] 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny', ], 'DEFAULT_RENDERER_CLASSES': [ # 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ], }
-
-
-
-
-
-
-
-
-
-
-
-
-
-