Django rest framework(三)-验证与权限

本文为学习官方文档时所做笔记,可以看做是官方文档的全文翻译

验证

基础信息

  • 两个重要属性

    • request.user:请求验证的用户,验证成功后通常为contrib.auth 下的User 类的实例
    • request.auth :携带的额外验证信息,比如可以代表获得过签名的请求的验证令牌
  • 概述:REST框架会尝试对验证列表中的每个验证类进行身份验证 ,返回成功验证的第一个验证类的值 ,若没有验证成功或者没有设置验证类那么request.user会设置为django.contrib.auth.models.AnonymousUserrequest.auth 会设置为None

  • 配置全局/默认验证方案DEFAULT_AUTHENTICATION_CLASSES

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework.authentication.BasicAuthentication',
            'rest_framework.authentication.SessionAuthentication',
        ]
    }
    
  • 为每个视图函数或视图图设置单独的验证方案

    from rest_framework.authentication import SessionAuthentication, BasicAuthentication
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.response import Response
    from rest_framework.views import APIView
    
    class ExampleView(APIView):
        authentication_classes = [SessionAuthentication, BasicAuthentication]
        permission_classes = [IsAuthenticated]
    
        def get(self, request, format=None):
            content = {
                'user': unicode(request.user),  # `django.contrib.auth.User` instance.
                'auth': unicode(request.auth),  # None
            }
            return Response(content)
    
    @api_view(['GET'])
    @authentication_classes([SessionAuthentication, BasicAuthentication])
    @permission_classes([IsAuthenticated])
    def example_view(request, format=None):
        content = {
            'user': unicode(request.user),  # `django.contrib.auth.User` instance.
            'auth': unicode(request.auth),  # None
        }
        return Response(content)
    
  • 验证失败或权限不足的响应:当未经身份验证的请求被拒绝许可时,可能有两种不同的错误代码

    • HTTP 401 Unauthorized:验证成败,响应头通常包括了WWW-Authenticate
    • HTTP 403 Permission Denied:验证失败,响应头不包括了WWW-Authenticate

    注意:返回何种错误代码取决于使用的验证方案,虽然可能使用多种身份验证方案,但只能使用一种方案来确定响应的类型,在确定响应类型时将使用视图上设置的第一个身份验证类;但是当身份验证成功依然拒绝访问时通常使用的是HTTP 403 Permission Denied进行错误响应

常用API

  • BasicAuthentication:根据用户名和密码颁发签名,通常只用于测试

    • 验证成功后返回:
      • request.user :Django的user对应实例
      • request.authNone
    • 验证失败后:
      • HTTP 401 Unauthorized
    • 注意:若在生产环境中使用BasicAuthentication必须保证所有的连接都是基于https 并且保证客户端每次连接时都要输入用户名和密码进行登陆,还必须保证客户端永远不会将这些细节存储到持久存储中。
  • TokenAuthentication:使用简单的基于令牌验证方式,令牌身份验证适用于客户端-服务器模式

    • 使用配置:

      #1.首先需要在setting中配置应用
      INSTALLED_APPS = [
          ...
          'rest_framework.authtoken'
      ]
      
      #2.配置全局验证方式或单独的视图验证方式
      #3.manage.py migrate进行数据库迁移
      
      #4.需要为每个用户创建token,
      from rest_framework.authtoken.models import Token
      token = Token.objects.create(user=...)
      
      • 捕捉post_save 信号为每个新注册用户自动创建令牌

        from django.conf import settings
        from django.db.models.signals import post_save
        from django.dispatch import receiver
        from rest_framework.authtoken.models import Token
        
        @receiver(post_save,sender=settings.AUTH_USER_MODEL)
        def create_auth_token(sender,instance=None,created=False,**kwargs):
            if created:
                Token.objects.create(user=instance)
        
        
      • 如果需要为已经存在的用户创建令牌

        from django.contrib.auth.models import User
        from rest_framework.authtoken.models import Token
        
        for user in User.objects.all():
            Token.objects.get_or_create(user=user)
        
      • 使用内置视图obtain_auth_token 创建一个可供请求的api:客户端请求该api并提供用户名和密码将获得对应的token

        from rest_framework.authtoken import views
        
        urlpatterns += [
            url(r'^api-token-auth/', views.obtain_auth_token)
        ]
        #注意:url部分可自定义
        

        当使用表单或者JSON将usernamepassword请求(POST)到该api时,验证成功后将返回一个JSON格式的token信息

        { 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }
        

        注意:默认情况下该api没有permissions throttling 限制,如果需要可以重写obtain_auth_token并添加throttle_classespermission_classes;如果希望token中不仅携带usernamepassword等信息还能实现更复杂的逻辑如前文的permissions throttling 限制,那么可以子类化ObtainAuthToken 并自定义,如下:

        from rest_framework.authtoken.views import ObtainAuthToken
        from rest_framework.authtoken.models import Token
        from rest_framework.response import Response
        
        class CustomAuthToken(ObtainAuthToken):
        
            def post(self, request, *args, **kwargs):
                serializer = self.serializer_class(data=request.data,
                                                   context={'request': request})
                serializer.is_valid(raise_exception=True)
                user = serializer.validated_data['user']
                token, created = Token.objects.get_or_create(user=user)
                return Response({
                    'token': token.key,
                    'user_id': user.pk,
                    'email': user.email
                })
        

        url.py中进行修改

        urlpatterns += [
            url(r'^api-token-auth/', CustomAuthToken.as_view())
        ]
        
      • 通过后台管理界面手动创建token:根据需求进行自定义配置

      • 控制台命令手动返回或生成token

        ./manage.py drf_create_token <username> #已经存在则返回,否则则创建
        ./manage.py drf_create_token -r <username> #直接创建
        
    • 响应头将携带生成的token传递给前端

      Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
      
    • 验证成功

      • request.user :Django的user对应实例
      • request.auth :对应的rest_framework.authtoken.models.Token 实例,即令牌信息
    • 验证失败:HTTP 401 Unauthorized ,并且令牌信息为空

      WWW-Authenticate: Token
      
    • 注意:生产环境下必须保证所有连接都基于https

  • SessionAuthentication:使用Django默认的session机制进行验证

    • 验证成功
      • request.user :Django的user对应实例
      • request.authNone
    • 验证失败:HTTP 403 Forbidden
    • 注意:使用AJAX请求访问API时,你需要保证为所有不安全的HTTP方法如PUT, PATCH, POST 或者DELETE 都使用的CSRF token来保证安全
  • RemoteUserAuthentication:允许您将身份验证委托给web服务器 ,该服务器需要设置REMOTE_USER环境变量。

自定义验证

  • 子类化BaseAuthentication 并重写authenticate(self, request) 方法,验证成功返回(user, auth) 二元数组,失败返回None或者直接产生AuthenticationFailed 验证失败。

    • 如果没有尝试身份验证就返回None,那么REST框架会尝试使用其他验证方案继续进行验证
    • 如果尝试了验证但失败,此时无论是否还有其他验证方案都会直接引发AuthenticationFailed 或返回None
  • 除了authenticate(self, request) 方法还可以通过重写authenticate_header(self, request) 方法来决定验证失败后响应头HTTP 401 UnauthorizedWWW-Authenticate 的值;若没有重新则会返回HTTP 403 Forbidden

  • 例子:

    from django.contrib.auth.models import User
    from rest_framework import authentication
    from rest_framework import exceptions
    
    class ExampleAuthentication(authentication.BaseAuthentication):
        def authenticate(self, request):
            username = request.META.get('HTTP_X_USERNAME')
            if not username:
                return None
            
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                raise exceptions.AuthenticationFailed('No such user')
                
            return (user,None)
    

第三方包

  • Django OAuth Toolkitpip install django-oauth-toolkit

  • Django REST framework OAuthpip install djangorestframework-oauth

  • JSON Web Token Authentication:基于token的验证方式,与REST框架相比好处是不需要维持一个数据库来验证token信息。djangorestframework-simplejwt

  • Hawk HTTP Authentication

  • HTTP Signature Authentication

  • Djoser:提供一组视图来处理基本操作,如注册、登录、注销、密码重置和帐户激活 ,该包使用自定义用户模型,并使用基于可供选择的令牌进行验证。文档: https://djoser.readthedocs.io/en/latest/getting_started.html

  • django-rest-auth:提供一组REST风格的 API端点,用于注册、身份验证(包括社交媒体身份验证)、密码重置、检索和更新用户详细信息等功能。通过这些API端点,您的客户端应用程序(如AngularJS、iOS、Android等不同客户端)都可以通过REST API独立地与Django后端站点通信,以进行用户管理。 文档:http://django-rest-auth.readthedocs.org/en/latest/

  • django-rest-framework-social-oauth2:以简单的方式将第三方社交软件集成到验证系统

  • django-rest-knox:提供自定义的模型和视图来处理基于令牌的身份验证,比内置的令牌身份验证方案拥有更高的安全性和可扩展性——考虑到单页面应用程序和移动客户端 。它为每个不同的客户端都提供令牌,以及在提供其他身份验证(通常是基本身份验证)时生成令牌的视图、删除令牌(提供服务器强制注销)和删除所有令牌(注销用户登录的所有客户端)。 DRF内置的令牌认证系统有以下弊端,但django-rest-knox都解决了它们:

    • 内置的令牌认证系统使得每个用户只能有一个令牌。因为令牌是共享的,所以这不利于从多个设备安全地登录。通常如果服务器端注销令牌(即删除令牌),此时所以端都会同时退出。
    • DRF内置令牌信息并未加密存储在数据库中。这将允许攻击者在数据库被破坏时不受限制地访问带有令牌的帐户。
    • DRF内置令牌会记录它们的创建时间,但没有令牌过期的内建机制。

    文档:http://james1345.github.io/django-rest-knox/

  • drfpasswordless:为REST框架内置的TokenAuthentication方案增加了无密码支持 。用户登录、注册时,需要向联系人的电子邮件地址或手机号码发送一个回叫令牌,用户将其正确地回传給服务器,服务器就会颁发一个身份验证令牌(基于内置的TokenAuthentication系统)。

权限

基础信息

  • 权限检查总是在视图的最开始运行,然后再允许其他代码继续运行。权限检查通常使用请求中的身份验证信息request.userrequest.auth进行判断 是否允许传入请求。

  • 权限级别:除了通常的对登陆等信息的权限验证,还支持对对象级别的权限验证,对象级权限用于确定是否允许用户对特定对象(通常为模型实例)进行操作。

    • 对象级的权限:当用户调用get_object()时,REST框架的通用视图支持对此动作进行权限判断,如果不允许用户操作给定对象,将引发PermissionDenied异常

    • 自定义对象级的权限:在通用视图上重写get_object 方法并在方法内部显式地调用check_object_permissions(request, obj) 方法

      '''
      权限验证成功:返回所调用的对象
      权限验证失败:引发PermissionDenied 或 NotAuthenticated异常
      '''
      def get_object(self):
          #obj为操作的对象
          obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"])
          self.check_object_permissions(self.request, obj)
          return obj
      

      注意:如果您希望使用提供的其他权限类来检查对象权限,则必须实现后文自定义权限部分中描述的has_object_permission()方法;通常,在使用对象级权限时还需要适当地筛选查询集,以确保用户只能看到允许他们查看的实例,如上文中提供了pk=self.kwargs["pk"]

  • 权限设置

    • 全局默认权限

      REST_FRAMEWORK = {
          'DEFAULT_PERMISSION_CLASSES': [
              'rest_framework.permissions.IsAuthenticated',
          ]
      }
      
    • 若没有配置,则默认为不受限制的访问

      'DEFAULT_PERMISSION_CLASSES': [
         'rest_framework.permissions.AllowAny',
      ]
      
    • 为单个视图设置权限:单独设置将使全局配置在该视图上失效。

      ##viewset
      from rest_framework.permissions import IsAuthenticated
      from rest_framework.response import Response
      from rest_framework.views import APIView
      
      class ExampleView(APIView):
          permission_classes = [IsAuthenticated]
      
          def get(self, request, format=None):
              content = {
                  'status': 'request was permitted'
              }
              return Response(content)
      
      #view
      from rest_framework.decorators import api_view, permission_classes
      from rest_framework.permissions import IsAuthenticated
      from rest_framework.response import Response
      
      @api_view(['GET'])
      @permission_classes([IsAuthenticated])
      def example_view(request, format=None):
          content = {
              'status': 'request was permitted'
          }
          return Response(content)
      
    • 继承自rest_framework.permissions.BasePermission 的权限类支持使用内置位操作符进行组合

      from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS
      from rest_framework.response import Response
      from rest_framework.views import APIView
      
      class ReadOnly(BasePermission):
          def has_permission(self, request, view):
              return request.method in SAFE_METHODS
          
      class ExampleView(APIView):
          permission_classes = [IsAuthenticated|ReadOnly] #使用 | 操作符,同时支持&和~
      
          def get(self, request, format=None):
              content = {
                  'status': 'request was permitted'
              }
              return Response(content)
      

常用API

  • AllowAny:将允许不受限制的访问,无论请求是否经过身份验证。这个权限可以通过使用空列表或元组设置权限来实现相同的结果,但是您可能会发现指定这个类很有用,因为它是显式表达的。

  • IsAuthenticated :只允许经过身份验证的用户访问

  • IsAdminUser :只允许由受信任的管理员访问,即user.is_staffTrue 的用户

  • IsAuthenticatedOrReadOnly :将允许经过身份验证的用户执行任何请求 ;未经过身份验证的用户只允许使用“安全”的请求方法进行访问,如GET, HEADOPTIONS

  • DjangoModelPermissions :本权限与django标准的django.contrib.auth 中的模型权限绑定;此权限只能应用于具有queryset属性集视图,有当用户经过身份验证并分配了相关的模型权限时,才会授予授权。

    • POST 请求要求用户对模型具有add权限

    • PUTPATCH 请求要求用户对模型具有change权限

    • DELETE 请求要求用户对模型具有delete权限

    • 同样支持在模型权限中重写默认权限以支持自定义模型权限,比如为GET方法设计一个权限。自定义模型权限请重写DjangoModelPermissions 并设置perms_map 属性

    • 注意:如果queryset 属性并没有被显式地提供而是通过get_queryset() 方法提供,那么需要设置一个哨兵queryset 属性以便该类可以确定所需的权限 ,尽管设置的queryset 属性并没有起作用

      queryset = User.objects.none()  # Required for DjangoModelPermissions
      
  • DjangoModelPermissionsOrAnonReadOnly:与DjangoModelPermissions相似,但允许未经验证的用户进行只读操作

  • DjangoObjectPermissions:本权限类与Django的标准对象权限框架绑定在一起 ,为了更好地使用这个权限类,您可以添加一个支持对象级权限的第三方包,例如django-guardian.。与DjangoModelPermissions相同,本权限必须应用在具有 queryset 属性或get_queryset() 方法的视图上,只有在用户经过身份验证并分配了相关的每个对象权限和相关模型权限时,才会授予授权。

    • POST 请求要求用户对模型实例具有add权限
    • PUTPATCH 请求要求用户对模型实例具有change权限
    • DELETE 请求要求用户对模型实例具有delete权限
    • DjangoModelPermissions一样,您可以通过覆盖DjangoObjectPermissions并设置perms_map属性来使用自定义对象级权限
    • 如果你需要为GET, HEADOPTIONS 赋予对象级的view 权限,并正在使用django-guardian作为的对象级权限后端, 那么可以进一步考虑使用djangorestframework-guardian 第三方包提供的DjangoObjectPermissionsFilter 类,该类确保端点只返回结果,包括用户具有对应对象的权限时

自定义权限

  • 要实现自定义权限,请重写BasePermission并实现以下方法中的一种或两种:

    • has_permission(self, request, view)
    • has_object_permission(self, request, view, obj)

    权限通过返回True,否则返回False;如果需要测试请求是读操作还是写操作,那么应该检查请求方法是否符合常量SAFE_METHODS,该常量是包含 'GET', 'OPTIONS' and 'HEAD' 的元组 ,比如

    if request.method in permissions.SAFE_METHODS:
        # Check permissions for read-only request
    else:
        # Check permissions for write request
    

    has_object_permission 对象级权限检查只有在has_permission权限通过后才被唤醒,还要注意,为了运行该权限检查,视图代码应该显式调用check_object_permissions(request, obj) 方法,如果您使用的是通用视图,那么默认情况下会自动为您处理(基于函数的视图将需要显式地检查对象权限,如果失败将引发PermissionDenied) 。

    如果希望自定义验证失败时的异常信息PermissionDenied ,那么需要实现message 属性

    class CustomerAccessPermission(permissions.BasePermission):
        message = 'Adding customers not allowed.'
    
        def has_permission(self, request, view):
    
  • 自定义权限案例:根据黑名单检查传入请求的IP地址,如果IP已被列入黑名单,则拒绝该请求。

    from rest_framework import permissions
    
    class BlacklistPermission(permissions.BasePermission):
        """
        Global permission check for blacklisted IPs.
        """
    
        def has_permission(self, request, view):
            ip_addr = request.META['REMOTE_ADDR']
            blacklisted = Blacklist.objects.filter(ip_addr=ip_addr).exists()
            return not blacklisted
    
  • 自定义对象级权限案例:仅允许对象的所有者编辑该对象

    class IsOwnerOrReadOnly(permissions.BasePermission):
        """
        Object-level permission to only allow owners of an object to edit it.
        Assumes the model instance has an `owner` attribute.
        """
    
        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
    
            # Instance must have an attribute named `owner`.
            return obj.owner == request.user
    

    请注意,通用视图通常将检查适当的对象级权限,但如果您正在编写自己的自定义视图,则需要确保自己检查对象级权限 ,通常可以通过调用self.check_object_permissions(request, obj) 来达到目的

    def get_object(self):
        #obj为操作的对象
        obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"])
        self.check_object_permissions(self.request, obj)
        return obj
    

    还要注意的是,通用视图只会检查检索单个模型实例的视图的对象级权限 ,如果需要对列表视图进行对象级筛选,则需要单独筛选查询集

第三方包

  • Django REST - Access Policy ,这个项目为Django REST框架项目提供了一种有序的访问控制管理方法。可以在一个声明型类中为每个ViewSet 或基于函数的view 分配权限策略。不再需要通过分散的视图或seralalizer来理解访问逻辑——它们都放在一个地方,以一种技术含量较低的涉众能够理解的格式

    class ArticleAccessPolicy(AccessPolicy):
        statements = [
            {
                "action": ["list", "retrieve"],
                "principal": "*",
                "effect": "allow"
            },
            {
                "action": ["publish", "unpublish"],
                "principal": ["group:editor"],
                "effect": "allow"            
            }
        ]
    
  • Composed Permissions ,提供一种简单的方法来定义复杂的多深度(使用逻辑操作符)权限对象,使用小型和可重用的组件。

  • REST Condition ,以简单方便的方式构建复杂权限的扩展。扩展将允许您将权限与逻辑操作符组合在一起。

  • DRY Rest Permissions ,这个框架非常适合那些有很多表以及它们之间的关系的应用程序。它提供了一个框架,允许您根据数据库中的现有数据为每个操作或操作组定义用户拥有的权限。

  • Django Rest Framework Roles :当您的数据模型中有不止一种类型的用户,并且您的业务逻辑是根据用户的类型而决定的,但您不希望手动输入大量围绕用户角色类型的条件分支,此时该包就能起作用。

  • Django REST Framework API Key

  • Django Rest Framework Role Filters ,提供对多种类型用户的简单筛选

posted @ 2020-06-29 17:59  言兴  阅读(511)  评论(0)    收藏  举报