news 2026/9/19 17:13:05

Django REST Framework 教程 4:为 API 添加认证(Authentication)与权限(Permissions)控制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django REST Framework 教程 4:为 API 添加认证(Authentication)与权限(Permissions)控制

Django REST Framework 教程 4:为 API 添加认证(Authentication)与权限(Permissions)控制

【免费下载链接】django-rest-frameworkWeb APIs for Django. 🎸项目地址: https://gitcode.com/gh_mirrors/dj/django-rest-framework

本篇是 Django REST Framework 官方教程系列的第 4 部分(前序为 Tutorial 1: Serialization、Tutorial 2: Requests and Responses、Tutorial 3: Class-based Views)。在完成了基于类视图的代码片段 API 之后,本节将系统讲解如何为 Web API 引入用户体系与细粒度访问控制:让每个代码片段归属其创建者、仅允许已认证用户创建资源、仅允许资源所有者修改或删除,同时保证未认证请求仍可只读访问。学完本篇,你将掌握 DRF 的视图级权限、对象级权限、自定义权限类、Browsable API 登录集成,以及基于 HTTP Basic/Session 的程序化认证调用。

本节要解决的四个问题

当前我们的 API 对任何人都开放编辑和删除权限,这显然不够安全。教程为本节设定了明确的目标:

  • 代码片段(snippet)始终与一个创建者(creator)关联;
  • 只有已认证用户才能创建片段;
  • 只有片段的创建者本人才能更新或删除它;
  • 未认证请求拥有完整的只读访问权。

这四个目标分别对应了"模型关联、视图权限、对象权限、匿名只读"四层工作,下面逐一实现。

第一步:扩展模型,让每个片段都归属一个用户

添加 owner 与 highlighted 字段

Snippet模型中增加两个字段:一个用于记录创建该片段的用户,另一个用于存储代码的语法高亮 HTML 表示。在snippets/models.py中添加:

owner = models.ForeignKey( "auth.User", related_name="snippets", on_delete=models.CASCADE ) highlighted = models.TextField()

要点说明:

  • owner使用字符串引用"auth.User",可以避免在模型模块顶部导入User时产生的循环依赖问题;
  • related_name="snippets"非常关键:它定义了从User反向查询其拥有片段集合的名字(即user.snippets),稍后编写UserSerializer时会用到;
  • on_delete=models.CASCADE表示用户被删除时其名下片段一并删除。

用 Pygments 自动生成高亮 HTML

为了让 API 后续能直接展示带语法高亮的代码,我们需要在模型保存时用pygments代码高亮库填充highlighted字段。先补充导入:

from pygments.lexers import get_lexer_by_name from pygments.formatters.html import HtmlFormatter from pygments import highlight

然后在模型类中添加.save()方法:

def save(self, *args, **kwargs): """ Use the `pygments` library to create a highlighted HTML representation of the code snippet. """ lexer = get_lexer_by_name(self.language) linenos = "table" if self.linenos else False options = {"title": self.title} if self.title else {} formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options) self.highlighted = highlight(self.code, lexer, formatter) super().save(*args, **kwargs)

这段逻辑的输入全部来自Snippet模型既有的字段:language决定词法分析器(lexer),linenos决定是否输出行号("table"表示用表格形式呈现行号),titlestyle则传给HtmlFormatter控制标题和配色主题。

更新数据库

模型结构变了,需要同步数据库。正式项目中应当编写迁移文件,但为了教程简洁,这里直接删除数据库重新初始化:

rm -f db.sqlite3 rm -r snippets/migrations python manage.py makemigrations snippets python manage.py migrate

提示:在实际项目中请勿删除迁移目录,应使用python manage.py makemigrations snippets正常生成迁移。教程这样做只是为了快速重置演示数据。

为了方便后续测试,再创建几个不同的用户:

python manage.py createsuperuser

第二步:为 User 模型添加 API 端点

编写 UserSerializer

有了用户数据,就需要把用户也暴露到 API 中。在snippets/serializers.py中添加:

from django.contrib.auth.models import User class UserSerializer(serializers.ModelSerializer): snippets = serializers.PrimaryKeyRelatedField( many=True, queryset=Snippet.objects.all() ) class Meta: model = User fields = ["id", "username", "snippets"]

这里有一个容易踩坑的知识点:snippetsUser模型上的反向关系(由Snippet.ownerrelated_name定义),因此ModelSerializer默认不会自动把它包含进来,必须显式声明字段。PrimaryKeyRelatedField(many=True)表示序列化时输出一组主键(即用户所拥有片段的 id 列表);其实现位于 rest_framework/relations.py,当传入的主键不存在时会抛出Invalid pk ... - object does not exist之类的校验错误。

编写只读的用户视图

snippets/views.py中添加两个只读的泛型类视图——用户信息只需读取,不需要增删改,因此选用ListAPIViewRetrieveAPIView

from django.contrib.auth.models import User class UserList(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer class UserDetail(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer

别忘了同时导入UserSerializer

from snippets.serializers import UserSerializer

这两个视图类分别对应 rest_framework/generics.py 中的ListAPIViewRetrieveAPIView:它们各自只绑定了一个 HTTP 方法(get),并分别组合了ListModelMixinRetrieveModelMixin,因此天然就是只读的。

注册用户端点

最后在snippets/urls.pyurlpatterns中添加入口:

path("users/", views.UserList.as_view()), path("users/<int:pk>/", views.UserDetail.as_view()),

第三步:通过 perform_create 把创建者与片段关联

现在的数据流存在一个缺口:客户端提交的 JSON 里不会、也不应该包含"创建者是谁"——用户身份是请求(request)的固有属性,而不是序列化数据的一部分。DRF 提供的解决方案是重写视图的.perform_create()方法,它允许我们介入实例保存过程,注入请求或 URL 中隐含的信息。

SnippetList视图类中添加:

def perform_create(self, serializer): serializer.save(owner=self.request.user)

CreateModelMixin.create()完成数据校验后,会调用perform_create(serializer),这里把self.request.user作为额外的owner关键字参数传给serializer.save()。于是序列化器的create()方法收到的是"校验后的数据 + owner 字段",最终生成的Snippet实例就与当前登录用户正确关联。

这一模式在后续教程中还会反复出现:任何"请求隐含信息"(如当前用户、URL 参数、客户端 IP)都可以通过重写perform_create/perform_update注入。

第四步:更新序列化器,用 ReadOnlyField 暴露 owner

现在片段已经与用户关联,还需要让 API 响应体现出这一点。在SnippetSerializer中新增字段:

owner = serializers.ReadOnlyField(source="owner.username")

注意:同时要在内部Meta类的fields列表中加入'owner',

这个字段背后有几个值得深挖的设计:

  • source参数决定该字段从实例的哪个属性取值,可以指向被序列化实例上的任意属性,也支持上面这种点号记法——DRF 会像 Django 模板语言那样逐级遍历属性(instance.owner.username)。
  • 无类型的ReadOnlyField:与CharFieldBooleanField等类型化字段不同,ReadOnlyField不关心类型,它"永远只读"——只参与序列化输出,反序列化(写入)时会被忽略,绝不会用于更新模型实例。其源码实现位于 rest_framework/fields.py:__init__中强制设置kwargs['read_only'] = Trueto_representation直接原样返回取值。
  • 本示例完全等价于owner = serializers.CharField(read_only=True),但用ReadOnlyField语义更直白。

第五步:视图级权限——IsAuthenticatedOrReadOnly

片段归属用户后,就要开始收紧写权限:只有已认证用户能创建、更新、删除片段。REST framework 内置了一系列可直接使用的权限类,本节选用的是IsAuthenticatedOrReadOnly:已认证请求获得读写权限,未认证请求只能读。

先在snippets/views.py中导入:

from rest_framework import permissions

然后给SnippetListSnippetDetail两个视图类都加上:

permission_classes = [permissions.IsAuthenticatedOrReadOnly]

从源码看,IsAuthenticatedOrReadOnly的实现位于 rest_framework/permissions.py:

def has_permission(self, request, view): return bool( request.method in SAFE_METHODS or request.user and request.user.is_authenticated )

其中SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')定义在 rest_framework/permissions.py,即所有安全方法无条件放行,其余方法要求用户已认证。permission_classes列表由视图基类在请求进入时逐个实例化并调用其has_permission();而对象级检查(has_object_permission)则在GenericAPIView.get_object()内部通过self.check_object_permissions(self.request, obj)触发,见 rest_framework/generics.py。

顺带认识其他内置权限类

同一文件 rest_framework/permissions.py 中还提供了:

权限类行为
AllowAny允许所有访问(显式声明意图,等价于空列表)
IsAuthenticated仅允许已认证用户
IsAdminUser仅允许is_staff为真的管理员
IsAuthenticatedOrReadOnly已认证可读写,匿名仅可读(本节所用)
DjangoModelPermissions结合 Django 的add/change/delete模型权限,通过perms_map把 HTTP 方法映射为权限码(POST → app.add_modelPUT/PATCH → app.change_modelDELETE → app.delete_model
DjangoModelPermissionsOrAnonReadOnly同上,但匿名用户可只读访问
DjangoObjectPermissions对象级权限,需要 Django Guardian 之类的后端支持

此外BasePermission的元类(rest_framework/permissions.py)还通过OperationHolderMixin实现了&(AND)、|(OR)、~(NOT)运算符,支持把多个权限类组合成表达式,例如permission_classes = [IsOwnerOrReadOnly & IsAuthenticated]。内置权限类行为均有对应的测试覆盖,可参阅 tests/test_permissions.py。

第六步:为 Browsable API 添加登录入口

应用IsAuthenticatedOrReadOnly后,如果你在浏览器中打开 Browsable API,会发现已经无法创建新的代码片段了——要恢复创建能力,必须先以某个用户身份登录。

在项目级的tutorial/urls.py顶部补充导入:

from django.urls import path, include

并在文件末尾追加一个包含 DRF 登录/登出视图的 URL 模式:

urlpatterns += [ path("api-auth/", include("rest_framework.urls")), ]

其中'api-auth/'前缀可以换成任何你喜欢的路径。这组视图来自 rest_framework/urls.py:它注册了两个 Django 内置认证视图——login/(使用rest_framework/login.html模板渲染)和logout/。文档头部注释明确提醒:使用 Browsable API 的登录功能时,认证设置里必须包含SessionAuthentication

完成后刷新浏览器页面,右上角会出现 "Login" 链接。用之前createsuperuser创建的用户登录后即可重新创建片段。创建若干片段后访问/users/端点,可以看到每个用户的snippets字段中列出了与其关联的片段 id 列表——这正是第二步中PrimaryKeyRelatedField的输出效果。

第七步:对象级权限——自定义 IsOwnerOrReadOnly

目前权限粒度还不够细:我们希望所有片段对所有人可见,但只有创建者本人才能更新或删除某个片段。这属于对象级权限,需要自定义权限类。

在 snippets 应用中新建snippets/permissions.py

from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ Custom permission to only allow owners of an object to edit it. """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True # Write permissions are only allowed to the owner of the snippet. return obj.owner == request.user

工作原理解读:

  • 继承BasePermission后只需实现has_object_permission(self, request, view, obj),其中obj是当前被操作的具体模型实例;
  • 先判断request.method in permissions.SAFE_METHODS(GET/HEAD/OPTIONS),安全方法一律放行,实现"任何人可读";
  • 写操作(PUT/PATCH/DELETE)则比较obj.ownerrequest.user,只有两者相等才返回True。注意这里直接使用了第一步在模型上建立的owner外键,这也是为何必须先完成"片段关联用户"这一步。

然后把该权限应用到片段实例端点,修改SnippetDetailpermission_classes

permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

并导入:

from snippets.permissions import IsOwnerOrReadOnly

permission_classes列表中的权限按顺序执行,两个类需要同时通过(逻辑与)。再次打开浏览器你会发现:只有在以片段创建者身份登录时,实例端点页面上才会出现 'DELETE' 和 'PUT' 操作按钮——Browsable API 会根据对象级权限自动隐藏无权限的操作。

第八步:用 HTTP 客户端验证认证流程

权限生效后,所有修改操作都必须携带认证凭证。本教程没有显式配置 authentication classes,因此使用的是 DRF 默认认证方案:SessionAuthenticationBasicAuthentication

  • 浏览器交互:通过 Browsable API 登录后,浏览器会话(Session)自动为后续请求提供认证;
  • 程序化调用:需要在每次请求中显式携带认证凭据。

默认认证方案的实现可参见 rest_framework/authentication.py:BasicAuthentication从请求的Authorization头解析 base64 编码的username:password(rest_framework/authentication.py),SessionAuthentication则借助 Django 会话框架(rest_framework/authentication.py 起)。

使用 HTTPie 未认证地创建片段,会得到明确的错误响应:

http POST http://127.0.0.1:8000/snippets/ code="print(123)" { "detail": "Authentication credentials were not provided." }

通过-a(或--auth)参数附带用户名与密码后即可成功:

http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)" { "id": 1, "owner": "admin", "title": "foo", "code": "print(789)", "linenos": false, "language": "python", "style": "friendly" }

注意响应中的"owner": "admin"——这正是第四步ReadOnlyField(source="owner.username")的输出。整套认证与权限机制在仓库中都有对应测试验证,例如 tests/test_authentication.py 与 tests/test_permissions.py。

小结

至此,我们的 Web API 已经具备了一套相当精细的权限体系:

  • 每个片段都通过owner外键与其创建者关联,并在保存时自动生成语法高亮 HTML;
  • 用户与片段都有各自的 API 端点,用户端点只读并展示其名下片段 id;
  • 视图级使用IsAuthenticatedOrReadOnly保证匿名只读、登录可写;
  • 对象级使用自定义IsOwnerOrReadOnly保证只有创建者能修改或删除自己的片段;
  • Browsable API 集成了登录/登出入口,程序化客户端则通过 HTTP Basic 认证访问。

在 第 5 部分 中,我们将为高亮片段创建 HTML 端点,并引入超链接(Hyperlinking)来串联 API 内部的关系。更系统的认证方案(Token、JWT、自定义认证类等)可参考 Authentication 指南,更多内置与自定义权限的用法见 Permissions 指南。

【免费下载链接】django-rest-frameworkWeb APIs for Django. 🎸项目地址: https://gitcode.com/gh_mirrors/dj/django-rest-framework

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 17:13:00

HDFS到对象存储迁移:云原生时代存储底座重构之路

最近一年聊大数据架构&#xff0c;大家问得最多的一个问题就是&#xff1a;HDFS 到底还能不能留&#xff1f;乍一听有点反常识&#xff0c;毕竟过去十几年&#xff0c;大数据底座这个词几乎就是 HDFS 的代名词。但到了云原生阶段&#xff0c;事情确实起了变化。我手头好几个项目…

作者头像 李华
网站建设 2026/9/19 17:11:45

AzerothCore:WotLK 3.3.5a 私服实战上手指南

AzerothCore&#xff1a;WotLK 3.3.5a 私服实战上手指南 【免费下载链接】azerothcore-wotlk Complete Open Source and Modular solution for MMO 项目地址: https://gitcode.com/GitHub_Trending/az/azerothcore-wotlk 想拥有自己的巫妖王年代艾泽拉斯&#xff1f;Aze…

作者头像 李华
网站建设 2026/9/19 17:10:34

信息学竞赛“骗分”导论:从暴力枚举到打表贪心的得分策略

简介&#xff1a;《信息学-骗分导论.docx》是一份面向信息学竞赛参赛者的策略性得分指南&#xff0c;主要定位给算法基础薄弱、备赛经验不足或处于集训初期的选手&#xff0c;系统讲解在无法完整求解时如何借助多种技巧博取尽可能高的分数。文档从lzn定理引出“骗分”理念&…

作者头像 李华
网站建设 2026/9/19 17:09:44

Gemini 3 Pro 深度研究体验,模型通道改到 TaoToken 再复现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华