Django Rest Framework 自定义身份验证

Posted

技术标签:

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

Django rest framework: Custom Authentication

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

【问题讨论】:

【参考方案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 下,指向您的自定义身份验证类。

【讨论】:

以上是关于Django Rest Framework 自定义身份验证的主要内容,如果未能解决你的问题,请参考以下文章

python 使用Django Rest Framework在Django中自定义用户实现

Django rest framework 之 DictField、ListField、自定义字段

django-rest-framework 自定义视图集检索多个查找参数

Django Rest Framework 自定义身份验证

Django REST Framework 创建自定义用户

与 Django Rest Framework 的非用户连接的自定义身份验证