【问题标题】:Django Rest Framework custom authenticationDjango Rest Framework 自定义身份验证
【发布时间】:2015-12-26 23:33:26
【问题描述】:

Django rest framework: Custom Authentication

我想在我的 Django 应用程序中使用自定义身份验证,但找不到如何应用它。文档中给出的示例对我来说很清楚,但他们没有提到在哪里创建这个新类以及如何使用它。

【问题讨论】:

    标签: python django authentication django-rest-framework


    【解决方案1】:

    下面是一个简单的示例,可用于实现自定义身份验证。要访问端点,您必须在 POST 数据中传递用户名和密码。

    urls.py

    urlpatterns = [
        url(r'^stuff/', views.MyView.as_view()),
        ...
    ]
    

    views.py

    from rest_framework.response import Response
    from rest_framework.views import APIView    
    from rest_framework.permissions import IsAuthenticated
    from rest_framework import exceptions
    from rest_framework import authentication
    from django.contrib.auth import authenticate, get_user_model
    from rest_framework.authentication import SessionAuthentication
    
    
    class ExampleAuthentication(authentication.BaseAuthentication):
        def authenticate(self, request):
            # Get the username and password
            username = request.data.get('username', None)
            password = request.data.get('password', None)
    
            if not username or not password:
                raise exceptions.AuthenticationFailed(_('No credentials provided.'))
    
            credentials = {
                get_user_model().USERNAME_FIELD: username,
                'password': password
            }
    
            user = authenticate(**credentials)
    
            if user is None:
                raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
    
            if not user.is_active:
                raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
    
        return (user, None)  # authentication successful
    
    
    class MyView(APIView):
        authentication_classes = (SessionAuthentication, ExampleAuthentication,)
        permission_classes = (IsAuthenticated,)
    
        def post(self, request, format=None):    
            content = {
                'user': unicode(request.user),
                'auth': unicode(request.auth),  # None
            }
            return Response(content)
    

    卷曲

    curl -v -X POST http://localhost:8000/stuff/ -d 'username=my_username&password=my_password'
    

    【讨论】:

    • 为什么第二个返回的参数(auth)是None?
    【解决方案2】:

    我遇到了类似的情况,我必须实现自定义身份验证类。到达其余端点的请求使用basic auth,但用户名不是 django 用户。请求已根据settings.py 中配置的用户名和密码进行身份验证。这是我的实现:

    - 创建自定义身份验证类

    # myapp/api/auth.py
    
    """Custom authentication module"""
    
    import base64
    import binascii
    
    from django.conf import settings
    from django.utils.six import text_type
    from django.utils.translation import ugettext_lazy as _
    
    from rest_framework.authentication import BaseAuthentication
    from rest_framework import HTTP_HEADER_ENCODING, exceptions
    
    
    class CustomAuthentication(BaseAuthentication):
        """
        Custom authentication class.
        It will authenticate any incoming request
        as the user given by the username in a
        custom request header.
        """
    
        def authenticate(self, request):
            """
            Returns a `User` if a correct username and password have been supplied
            using HTTP Basic authentication.  Otherwise returns `None`.
            """
    
            # Gets authorization from request header
            # and checks different possibility of
            # invalid header.
            # ======================================
    
            auth = self.get_authorization_header(request).split()
    
            if not auth or auth[0].lower() != b"basic":
                raise exceptions.AuthenticationFailed(_("Invalid header!"))
    
            if len(auth) == 1:
                msg = _("Invalid basic header. No credentials provided.")
                raise exceptions.AuthenticationFailed(msg)
            elif len(auth) > 2:
                msg = _(
                    "Invalid basic header. Credentials string should not contain spaces."
                )
                raise exceptions.AuthenticationFailed(msg)
    
            try:
                auth_parts = (
                    base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(":")
                )
            except (TypeError, UnicodeDecodeError, binascii.Error):
                msg = _("Invalid basic header. Credentials not correctly base64 encoded.")
                raise exceptions.AuthenticationFailed(msg)
    
            # parses username and password.
            userid, password = auth_parts[0], auth_parts[2]
    
            if userid != settings.FERRATUM_CALLBACK_USERNAME:
                msg = _("Invalid basic header. Username is incorrect!")
                raise exceptions.AuthenticationFailed(msg)
    
            if password != settings.FERRATUM_CALLBACK_PASSWORD:
                msg = _("Invalid basic header. Password is incorrect!")
                raise exceptions.AuthenticationFailed(msg)
    
            # An user object is expected to be returned
            # in case of successful authentication. Therefore
            # a user object is returned with the give username
            # in the header. This user doesn't exists in the
            # django user User model.
            # ===============================================
    
            user = {
                "username": userid,
                "password": "",
                "email": "",
                "first_name": "",
                "last_name": "",
                "company": "",
            }
    
            return (user, None)
    
        @staticmethod
        def get_authorization_header(request):
            """
            Return request's 'Authorization:' header, as a bytestring.
    
            Hide some test client ickyness where the header can be unicode.
            """
    
            auth = request.META.get("HTTP_AUTHORIZATION", b"")
            if isinstance(auth, text_type):
                # Work around django test client oddness
                auth = auth.encode(HTTP_HEADER_ENCODING)
            return auth
    
    

    - 在 DEFAULT_AUTHENTICATION_CLASS 中添加它

    # myapp/settings.py
    
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": (
            "rest_framework.authentication.SessionAuthentication",
            "mozilla_django_oidc.contrib.drf.OIDCAuthentication",
            "rest_framework.authentication.BasicAuthentication",
            "users.auth.SaveriumTokenAuthentication",
            "api.auth.CustomAuthentication"
        ),
        'DEFAULT_RENDERER_CLASSES': DEFAULT_RENDERER_CLASSES
    }
    

    - 在视图中使用了自定义身份验证

    # myapp/api/view/api_view.py
    
    from api.auth import CustomAuthentication
    
    
    class UpdateStatus(APIView):
        """
        It updates application status
        """
        
        # Custom authentication scheme.
        authentication_classes = [CustomAuthentication]
    
        def post(self, *args, **kwargs):
            """
            Callback comes as a POST request
            with data in JSON format.
            """
    
            data = self.request.data
            ...
    

    【讨论】:

      【解决方案3】:

      我用了以下方式

      from rest_framework_jwt.settings import api_settings
      from rest_framework import status, generics
      
       class UserLogin(generics.CreateAPIView):
      
          def post(self, request, *args, **kwargs):
              email = request.data['email']
              if email is None:
                  return Response({'error': 'Email not informed'}, status=status.HTTP_403_FORBIDDEN)
              try:
                  user = User.objects.get(email=email)
                  if not user.check_password(request.data['password']):
                      return Response({'error': 'Email ou senha incorreto'}, status=status.HTTP_400_BAD_REQUEST)
                  jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
                  jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
                  payload = jwt_payload_handler(user)
                  token = jwt_encode_handler(payload)
                  return Response({"token": token, "user":UserSessionSerializerAuth(user,
                              context={'request': request}).data}, status=status.HTTP_200_OK)
              except User.DoesNotExist:
                  return Response({'error': 'User not found'}, status=status.HTTP_403_FORBIDDEN)
      

      【讨论】:

      • 虽然此代码可以回答问题,但提供有关 如何 和/或 为什么 解决问题的附加上下文将改善答案的长期价值。
      • 为什么要为这样的任务覆盖创建 apiview
      【解决方案4】:

      如何在 DRF 中实现自定义身份验证方案?

      要实现自定义身份验证方案,我们需要继承 DRF 的 BaseAuthentication 类并覆盖 .authenticate(self, request) 方法。

      如果认证成功,该方法应该返回一个二元组(user, auth),否则返回None。在某些情况下,我们可能会从 .authenticate() 方法中引发 AuthenticationFailed 异常。

      示例(来自DRF docs):

      假设我们想要将任何传入请求验证为由名为 'X_USERNAME' 的自定义请求标头中的 username 指定的用户。

      第 1 步:创建自定义身份验证类

      为此,我们将在my_app 中创建一个authentication.py 文件。

      # my_app/authentication.py
      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('X_USERNAME') # get the username request header
              if not username: # no username passed in request headers
                  return None # authentication did not succeed
      
              try:
                  user = User.objects.get(username=username) # get the user
              except User.DoesNotExist:
                  raise exceptions.AuthenticationFailed('No such user') # raise exception if user does not exist 
      
              return (user, None) # authentication successful
      

      第 2 步:指定自定义身份验证类

      创建自定义身份验证类后,我们需要在 DRF 设置中定义此身份验证类。这样做,所有请求都将基于此身份验证方案进行身份验证。

      'DEFAULT_AUTHENTICATION_CLASSES': (       
          'my_app.authentication.ExampleAuthentication', # custom authentication class
          ...
      ),
      

      注意:如果您想在基于每个视图或基于每个视图集而不是全局级别使用此自定义身份验证类,您可以定义此身份验证类明确地在你的观点中。

      class MyView(APIView):
      
          authentication_classes = (ExampleAuthentication,) # specify this authentication class in your view
      
          ...
      

      【讨论】:

      • 你能举例说明如何使用 curl 吗?
      • @momokjaaaaa 检查此 SO 链接以在 POST 请求中发送标头。 *.com/questions/356705/…
      • 我们在哪里检查密码?在他们给出的示例中,任何人都可以登录,这让一切变得更加混乱。
      【解决方案5】:

      在保存您的 api 文件的文件夹中,创建另一个文件来保存您的自定义身份验证类,例如 authentication.py。然后在您的设置中,在DEFAULT_AUTHENTICATION_CLASSES 下,指向您的自定义身份验证类。

      【讨论】: