认证机制是将传入的请求与一组用于识别身份的凭证(例如请求发起者或签名所用的令牌)相关联的方式。之后权限和限流策略可以依据这些凭证来判定请求是否被允许。

REST framework 提供了几种开箱即用的认证方案,同时也支持自定义方案。

认证总是在视图的最开始执行,在权限和限流检查之前,且在任何其他代码之前执行。

request.user 属性通常会被设置为 contrib.auth 包中 User 类的实例。

request.auth 属性则用于任何额外的认证信息,例如,它可以代表请求签名所使用的令牌。


注意 :单独的认证本身并不会允许或拒绝传入的请求,它只是识别请求所使用的凭证。

有关如何为你的 API 设置权限策略的详细信息,请参见权限文档。


认证的确定方式

认证方案总是被定义为一系列类。REST framework 将尝试使用列表中的每个类进行认证,并会使用第一个认证成功的类的返回值来设置 request.userrequest.auth

如果没有类认证成功,request.user 将被设置为 django.contrib.auth.models.AnonymousUser 的一个实例,而 request.auth 将被设置为 None

可以通过 UNAUTHENTICATED_USERUNAUTHENTICATED_TOKEN 设置来修改未认证请求的 request.userrequest.auth 的值。

设置认证方案

可以通过 DEFAULT_AUTHENTICATION_CLASSES 设置项来全局设置默认认证方案。例如:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ]
}

也可以在单个视图或视图集中设置认证方案,使用基于类的视图 APIView

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': str(request.user),  # `django.contrib.auth.User` 实例。
            'auth': str(request.auth),  # None
        }
        return Response(content)

或者,如果你使用的是带有 @api_view 装饰器的函数式视图。

@api_view(['GET'])
@authentication_classes([SessionAuthentication, BasicAuthentication])
@permission_classes([IsAuthenticated])
def example_view(request, format=None):
    content = {
        'user': str(request.user),  # `django.contrib.auth.User` 实例。
        'auth': str(request.auth),  # None
    }
    return Response(content)

当未认证的请求被拒绝权限时,可能会使用两种不同的错误码。

  • HTTP 401 未授权
  • HTTP 403 禁止访问

HTTP 401 响应必须始终包含 WWW-Authenticate 响头,该响头会指示客户端如何进行认证。HTTP 403 响应则不包含 WWW-Authenticate 响头。

所使用的响应类型取决于认证方案。尽管可以同时使用多种认证方案,但只有其中一个方案会被用来确定响应类型。视图上设置的第一个认证类将被用于确定响应类型

请注意,当请求可能认证成功,但仍被拒绝执行该请求的权限时,无论认证方案如何,始终会使用 403 禁止访问 响应。

Django 5.1 + LoginRequiredMiddleware

如果你使用的是 Django 5.1 + 并且启用了 LoginRequiredMiddleware,请注意,DRF 的所有视图都被豁免于此中间件。这是因为 DRF 的认证基于认证类和权限类,这些类可能在中间件应用之后才被确定。此外,当请求未认证时,中间件会将用户重定向到登录页面,这对于 API 请求来说是不合适的,因为 API 请求更倾向于返回 401 状态码。

REST framework 为 DRF 视图提供了一种等效机制,即通过全局设置项 DEFAULT_AUTHENTICATION_CLASSESDEFAULT_PERMISSION_CLASSES。如果需要强制 API 请求登录,应该相应地更改这些设置。

Apache mod_wsgi 特定配置

请注意,如果使用 mod_wsgi 部署到 Apache,授权响头默认不会被传递到 WSGI 应用,因为人们假设认证将由 Apache 而非应用层来处理。

如果你使用的是非基于会话的认证,并且将其部署到 Apache,那么需要显式地配置 mod_wsgi 以将所需的响头传递给应用。这可以通过在适当的上下文中指定 WSGIPassAuthorization 指令并将其设置为 'On' 来实现。

# 这可以放在服务器配置、虚拟主机、目录或 .htaccess 中
WSGIPassAuthorization On

API 参考

BasicAuthentication

这种认证方案使用 HTTP 基本身份验证,基于用户的用户名和密码进行签名。基本认证通常只适用于测试。

如果认证成功,BasicAuthentication 将提供以下凭证。

  • request.user 将是一个 Django User 实例。
  • request.auth 将为 None

对于未认证的请求,如果被拒绝权限,将返回带有适当的 WWW-Authenticate 响头的 HTTP 401 未授权 响应。例如:

WWW-Authenticate: Basic realm="api"

注意 :如果你在生产环境中使用 BasicAuthentication,必须确保你的 API 仅通过 https 提供服务。你还应确保你的 API 客户端在登录时始终重新请求用户名和密码,并且永远不会将这些详细信息存储到持久存储中。

TokenAuthentication


注意 :Django REST framework 提供的令牌认证是一种相对简单的实现。

对于允许每个用户拥有多个令牌、具有更严格的安全部署详细信息以及支持令牌过期的实现,请参见第三方包 Django REST Knox。


这种认证方案使用一种简单的基于令牌的 HTTP 认证方式。令牌认证适用于客户端 - 服务器架构,例如原生桌面和移动客户端。

要使用 TokenAuthentication 方案,你需要将 TokenAuthentication 添加到认证类中,并且需要将 rest_framework.authtoken 添加到你的 INSTALLED_APPS 设置项中:

INSTALLED_APPS = [
    ...
    'rest_framework.authtoken'
]

在更改设置后,别忘了运行 manage.py migrate

rest_framework.authtoken 应用提供了 Django 数据库迁移。

你还需要为用户创建令牌。

from rest_framework.authtoken.models import Token

token = Token.objects.create(user=...)
print(token.key)

客户端要进行身份验证,令牌密钥应包含在 Authorization HTTP 响头中。密钥应由字符串 "Token" 作为前缀,并且两者之间用空白字符分隔。例如:

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

如果你希望在响头中使用不同的关键字,例如 Bearer,只需子类化 TokenAuthentication 并设置 keyword 类变量即可。

如果认证成功,TokenAuthentication 将提供以下凭证。

  • request.user 将是一个 Django User 实例。
  • request.auth 将是一个 rest_framework.authtoken.models.Token 实例。

对于未认证的请求,如果被拒绝权限,将返回带有适当的 WWW-Authenticate 响头的 HTTP 401 未授权 响应。例如:

WWW-Authenticate: Token

curl 命令行工具对于测试令牌认证的 API 可能会很有用 。例如:

curl -X GET http://127.0.0.1:8000/api/example/ -H 'Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'

注意 :如果你在生产环境中使用 TokenAuthentication,必须确保你的 API 仅通过 https 提供服务。


生成令牌

通过使用信号

如果你希望每个用户都自动生成一个令牌,你可以简单地捕获用户的 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)

请注意,你需要将此代码片段放置在 Django 启动时会导入的已安装 models.py 模块或其他位置中。

如果你已经创建了一些用户,可以像这样为所有现有用户生成令牌:

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)

通过暴露 API 端点

当使用 TokenAuthentication 时,你可能希望通过提供用户名和密码的方式让客户端获取令牌。REST framework 提供了一个内置视图来实现此行为。要使用它,将 obtain_auth_token 视图添加到你的 URLconf 中:

from rest_framework.authtoken import views
urlpatterns += [
    path('api-token-auth/', views.obtain_auth_token)
]

请注意,URL 模式的 URL 部分可以是任何你想要使用的值。

当使用表单数据或 JSON 发送有效的 usernamepassword 字段的 POST 请求时,obtain_auth_token 视图将返回一个 JSON 响应:

{ 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }

请注意,默认的 obtain_auth_token 视图显式地使用 JSON 请求和响应,而不是使用设置中的默认渲染器和解析器类。

默认情况下,obtain_auth_token 视图没有应用任何权限或限流。如果你希望应用限流,需要通过子类化视图类并使用 throttle_classes 属性来覆盖它。

如果你需要自定义版本的 obtain_auth_token 视图,可以通过子类化 ObtainAuthToken 视图类,并在 urlconf 中使用它。

例如,你可以在返回值中包含除了 token 之外的更多用户信息:

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
        })

然后在你的 urls.py 中:

urlpatterns += [
    path('api-token-auth/', CustomAuthToken.as_view())
]

在 Django 管理界面中操作

也可以通过管理界面手动创建令牌。如果你正在处理大量用户,我们建议你通过声明 user 字段为 raw_field 来对 TokenAdmin 类进行 monkey patch。

your_app/admin.py

from rest_framework.authtoken.admin import TokenAdmin

TokenAdmin.raw_id_fields = ['user']

使用 Django manage.py 命令

从版本 3.6.4 开始,可以通过以下命令生成用户令牌:

./manage.py drf_create_token <username>

该命令将返回给定用户的 API 令牌,如果令牌不存在则创建它:

Generated token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b for user user1

如果你想要重新生成令牌(例如,令牌被泄露),可以传递一个额外的参数:

./manage.py drf_create_token -r <username>

SessionAuthentication

这种认证方案使用 Django 默认的会话后端进行认证。会话认证适用于运行在与网站相同会话上下文中的 AJAX 客户端。

如果认证成功,SessionAuthentication 将提供以下凭证。

  • request.user 将是一个 Django User 实例。
  • request.auth 将为 None

对于未认证的请求,如果被拒绝权限,将返回 HTTP 403 禁止访问 响应。

如果你正在使用会话认证的 AJAX 风格 API,那么在进行任何不安全的 HTTP 方法调用(如 PUTPATCHPOSTDELETE 请求)时,必须确保包含有效的 CSRF 令牌。有关详细信息,请参见 Django 的 CSRF 文档。

警告 :始终使用 Django 的标准登录视图来创建登录页面。这将确保你的登录视图得到适当的保护。

由于 REST framework 需要支持会话和非会话认证方式访问同一视图,因此其 CSRF 验证与标准 Django 略有不同。这意味着只有经过认证的请求需要 CSRF 令牌,而匿名请求可以不带 CSRF 令牌发送。此行为不适用于登录视图,登录视图应始终应用 CSRF 验证。

RemoteUserAuthentication

这种认证方案允许你将认证委托给你的 web 服务器,后者设置 REMOTE_USER 环境变量。

要使用它,你必须在 AUTHENTICATION_BACKENDS 设置项中包含 django.contrib.auth.backends.RemoteUserBackend(或其子类)。默认情况下,RemoteUserBackend 会为不存在的用户名创建 User 对象。要更改此行为及其他行为,请参阅 Django 文档。

如果认证成功,RemoteUserAuthentication 将提供以下凭证:

  • request.user 将是一个 Django User 实例。
  • request.auth 将为 None

有关配置身份验证方法的信息,请参阅你的 web 服务器的文档,例如:

  • Apache 认证指南
  • NGINX(限制访问)

自定义认证

要实现自定义认证方案,可以子类化 BaseAuthentication 并覆盖 .authenticate(self, request) 方法。该方法应在认证成功时返回一个包含 (user, auth) 的二元组,否则返回 None

在某些情况下,而不是返回 None,你可能希望从 .authenticate() 方法中引发 AuthenticationFailed 异常。

通常,你应该采取以下方法:

  • 如果未尝试认证,返回 None 。其他在用的认证方案仍会被检查。
  • 如果尝试了认证但失败,引发 AuthenticationFailed 异常。将立即返回错误响应,无论任何权限检查如何,并且不会检查其他认证方案。

你还可以覆盖 .authenticate_header(self, request) 方法。如果实现,它应返回一个字符串,该字符串将用作 HTTP 401 未授权 响应中的 WWW-Authenticate 响头的值。

如果未覆盖 .authenticate_header() 方法,则当未认证的请求被拒绝访问时,认证方案将返回 HTTP 403 禁止访问 响应。


注意 :当你的自定义认证器被请求对象的 .user.auth 属性调用时,你可能会看到 AttributeError 被重新抛出为 WrappedAttributeError 。这是必要的,以防止原始异常被外部属性访问所抑制。Python 将无法识别 AttributeError 来自你的自定义认证器,而是会假设请求对象没有 .user.auth 属性。这些错误应由你的认证器修复或以其他方式处理。


示例

以下示例将把任何传入请求认证为自定义请求头 X-USERNAME 中指定的用户名对应的用户。

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-rest-knox

Django-rest-knox 库提供了用于处理令牌认证的模型和视图,其方式比内置的 TokenAuthentication 方案更安全、更可扩展,适用于单页应用和移动客户端。它支持每个客户端具有各自的令牌,并提供用于生成(通常基于基本认证)、删除令牌(实现服务器强制的注销)和删除所有令牌(注销用户登录的所有客户端)的视图。

Django OAuth Toolkit 包提供 OAuth 2.0 支持,并且兼容 Python 3.4 + 。该包由 jazzband 维护,并使用出色的 OAuthLib。该包文档完善、支持良好,目前是推荐的 OAuth 2.0 支持包。

安装与配置

使用 pip 进行安装。

pip install django-oauth-toolkit

将该包添加到你的 INSTALLED_APPS 中,并修改 REST framework 设置。

INSTALLED_APPS = [
    ...
    'oauth2_provider',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
    ]
}

有关更多详细信息,请参见 Django REST framework - 入门文档。

Django REST framework OAuth

Django REST framework OAuth 包为 REST framework 提供了 OAuth1 和 OAuth2 支持。

这个包以前直接包含在 REST framework 中,但现在作为第三方包进行支持和维护。

安装与配置

使用 pip 安装该包。

pip install djangorestframework-oauth

有关配置和使用的详细信息,请参见 Django REST framework OAuth 文档中的认证和权限部分。

JSON Web Token 认证

JSON Web Token 是一种相对较新的标准,可用于令牌认证。与内置的 TokenAuthentication 方案不同,JWT 认证不需要使用数据库来验证令牌。djangorestframework-simplejwt 包提供了一些功能以及可插拔的令牌黑名单应用。

Hawk HTTP 认证

HawkREST 库基于 Mohawk 库,使你可以在 API 中处理 Hawk 签名的请求和响应。Hawk 使两方能够使用共享密钥安全地通信。它基于 HTTP MAC 访问认证(后者基于 OAuth 1.0 的部分内容)。

HTTP 签名认证

HTTP 签名(目前是 IETF 草案)提供了一种实现消息源认证和消息完整性的方法。类似于亚马逊的许多服务所使用的 HTTP 签名方案,它允许无状态的、每请求的认证。Elvio Toccalino 维护的 djangorestframework-httpsignature (已过时)包提供了一个易于使用的 HTTP 签名认证机制。可以使用 drf-httpsig 的更新版本。

Djoser

Djoser 库提供了一组视图来处理诸如注册、登录、注销、密码重置和账户激活等基本操作。该包与自定义用户模型兼容,并使用基于令牌的认证。这是一种 Django 认证系统的即用型 REST 实现。

django-rest-auth / dj-rest-auth

该库提供了一组 REST API 端点,用于注册、认证(包括社交媒体认证)、密码重置、检索和更新用户详细信息等。通过这些 API 端点,你的客户端应用(如 AngularJS、iOS、Android 等)可以独立地与 Django 后端通过 REST API 进行用户管理。

该项目目前有两个分支。

  • Django-rest-auth 是原始项目,但目前没有更新。
  • Dj-rest-auth 是该项目的一个较新的分支。

drf-social-oauth2 是一个框架,它可以帮助你使用主要的社交媒体 oauth2 供应商(如 Facebook、Google、Twitter、Orcid 等)进行认证 。它以 JWT 方式生成令牌,设置简便。

drfpasswordless

drfpasswordless 为 Django REST Framework 的 TokenAuthentication 方案添加了(类似 Medium、Square Cash 的)无密码支持。用户可以通过发送到联系点(如电子邮件地址或手机号码)的令牌进行登录和注册。

django-rest-authemail

django-rest-authemail 提供了一个用于用户注册和认证的 RESTful API 接口。它使用电子邮件地址进行认证,而不是用户名。API 端点可用于注册、注册邮件验证、登录、注销、密码重置、密码重置验证、电子邮件更改、电子邮件更改验证、密码更改和用户详情等操作,并且包含一个完全可运行的示例项目和详细说明。

Django-Rest-Durin

Django-Rest-Durin 的构建理念是拥有一个库,为多个 Web/CLI/移动 API 客户端提供令牌认证接口,同时允许为每个 API 客户端配置不同的令牌设置。它通过自定义模型、视图和权限为 Django-Rest-Framework 提供每个用户多个令牌的支持,每个 API 客户端的令牌过期时间可以不同,并且可以通过 Django 管理界面进行自定义。

更多详细信息可以在文档中找到。

django-pyoidc

[dango-pyoidc][django_pyoidc] 添加了对 OpenID Connect(OIDC)认证的支持。这允许你将用户管理委托给身份提供者,从而可以实现单点登录(SSO)。它支持大多数使用场景,例如自定义如何将令牌信息映射到用户模型、使用 OIDC 群众进行访问控制等。

更多详细信息可以在文档中找到。

[django-pyoidc]: https://github.com/makinacorpus/django_pyoidc


观复
16 声望1 粉丝

君子慎独,不欺暗室,卑以自牧,含章可贞