ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

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_namesnippets, on_deletemodels.CASCADE ) highlighted models.TextField()要点说明owner使用字符串引用auth.User可以避免在模型模块顶部导入User时产生的循环依赖问题related_namesnippets非常关键它定义了从User反向查询其拥有片段集合的名字即user.snippets稍后编写UserSerializer时会用到on_deletemodels.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(styleself.style, linenoslinenos, fullTrue, **options) self.highlighted highlight(self.code, lexer, formatter) super().save(*args, **kwargs)这段逻辑的输入全部来自Snippet模型既有的字段language决定词法分析器lexerlinenos决定是否输出行号table表示用表格形式呈现行号title与style则传给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( manyTrue, querysetSnippet.objects.all() ) class Meta: model User fields [id, username, snippets]这里有一个容易踩坑的知识点snippets是User模型上的反向关系由Snippet.owner的related_name定义因此ModelSerializer默认不会自动把它包含进来必须显式声明字段。PrimaryKeyRelatedField(manyTrue)表示序列化时输出一组主键即用户所拥有片段的 id 列表其实现位于 rest_framework/relations.py当传入的主键不存在时会抛出Invalid pk ... - object does not exist之类的校验错误。编写只读的用户视图在snippets/views.py中添加两个只读的泛型类视图——用户信息只需读取不需要增删改因此选用ListAPIView和RetrieveAPIViewfrom 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别忘了同时导入UserSerializerfrom snippets.serializers import UserSerializer这两个视图类分别对应 rest_framework/generics.py 中的ListAPIView与RetrieveAPIView它们各自只绑定了一个 HTTP 方法get并分别组合了ListModelMixin与RetrieveModelMixin因此天然就是只读的。注册用户端点最后在snippets/urls.py的urlpatterns中添加入口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(ownerself.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(sourceowner.username)注意同时要在内部Meta类的fields列表中加入owner,。这个字段背后有几个值得深挖的设计source参数决定该字段从实例的哪个属性取值可以指向被序列化实例上的任意属性也支持上面这种点号记法——DRF 会像 Django 模板语言那样逐级遍历属性instance.owner.username。无类型的ReadOnlyField与CharField、BooleanField等类型化字段不同ReadOnlyField不关心类型它永远只读——只参与序列化输出反序列化写入时会被忽略绝不会用于更新模型实例。其源码实现位于 rest_framework/fields.py__init__中强制设置kwargs[read_only] Trueto_representation直接原样返回取值。本示例完全等价于owner serializers.CharField(read_onlyTrue)但用ReadOnlyField语义更直白。第五步视图级权限——IsAuthenticatedOrReadOnly片段归属用户后就要开始收紧写权限只有已认证用户能创建、更新、删除片段。REST framework 内置了一系列可直接使用的权限类本节选用的是IsAuthenticatedOrReadOnly已认证请求获得读写权限未认证请求只能读。先在snippets/views.py中导入from rest_framework import permissions然后给SnippetList和SnippetDetail两个视图类都加上permission_classes [permissions.IsAuthenticatedOrReadOnly]从源码看IsAuthenticatedOrReadOnly的实现位于 rest_framework/permissions.pydef 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_model、PUT/PATCH → app.change_model、DELETE → app.delete_modelDjangoModelPermissionsOrAnonReadOnly同上但匿名用户可只读访问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.pyfrom 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 well 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_METHODSGET/HEAD/OPTIONS安全方法一律放行实现任何人可读写操作PUT/PATCH/DELETE则比较obj.owner与request.user只有两者相等才返回True。注意这里直接使用了第一步在模型上建立的owner外键这也是为何必须先完成片段关联用户这一步。然后把该权限应用到片段实例端点修改SnippetDetail的permission_classespermission_classes [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]并导入from snippets.permissions import IsOwnerOrReadOnlypermission_classes列表中的权限按顺序执行两个类需要同时通过逻辑与。再次打开浏览器你会发现只有在以片段创建者身份登录时实例端点页面上才会出现 DELETE 和 PUT 操作按钮——Browsable API 会根据对象级权限自动隐藏无权限的操作。第八步用 HTTP 客户端验证认证流程权限生效后所有修改操作都必须携带认证凭证。本教程没有显式配置 authentication classes因此使用的是 DRF 默认认证方案SessionAuthentication和BasicAuthentication。浏览器交互通过 Browsable API 登录后浏览器会话Session自动为后续请求提供认证程序化调用需要在每次请求中显式携带认证凭据。默认认证方案的实现可参见 rest_framework/authentication.pyBasicAuthentication从请求的Authorization头解析 base64 编码的username:passwordrest_framework/authentication.pySessionAuthentication则借助 Django 会话框架rest_framework/authentication.py 起。使用 HTTPie 未认证地创建片段会得到明确的错误响应http POST http://127.0.0.1:8000/snippets/ codeprint(123) { detail: Authentication credentials were not provided. }通过-a或--auth参数附带用户名与密码后即可成功http -a admin:password123 POST http://127.0.0.1:8000/snippets/ codeprint(789) { id: 1, owner: admin, title: foo, code: print(789), linenos: false, language: python, style: friendly }注意响应中的owner: admin——这正是第四步ReadOnlyField(sourceowner.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),仅供参考
返回列表