Django与DRF的认证架构

在开发系统过程中,免不了要自定义认证功能来实现特定的需求,比如在JWT认证系统中实现退出登录的功能。

然而Django和Django Rest Framework(DRF)采用了不同的认证架构,这让使用了DRF的项目实现认证功能时产生困惑。

Django的认证架构#

Django自带了通过用户名和密码进行认证的功能,认证后使用 session id 来认证当前用户。

可以通过自定义 authentication backends 的方法来对Django的认证架构进行扩展,实现项目的特定需求。

Django的认证是通过middleware的方式实现的。通常在 settings.py 文件中需要配置认证的middleware。

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    ...
]

Django在认证后,会设置 request.user 变量,需要注意的是,如果自己编写的 middleware 要获取认证后的用户时, 需要将自己编写的 middleware 放在 django.contrib.auth.middleware.AuthenticationMiddleware 后面,否则无法获取到认证的用户。

在另一方面,如果想要实现登录用户对不同 view 的权限,则需要在 view 的代码中进行权限检查。

Django也提供了一些方便使用的decorator和mixin:https://docs.djangoproject.com/en/4.1/topics/auth/default/#limiting-access-to-logged-in-users

但都是需要在 view 的代码中显示的去使用的,也就是黑名单模式,如果开发人员忘记设置权限检查,则意味着没有权限检查。

DRF的认证架构#

DRF的认证和授权是在其 APIView 类中实现的, APIView 是所有 DRF 接口的基类。

其认证后的用户也可以通过 request.user 变量访问,值得注意的是,DRF的 request 是对 Django 的 request 的封装,并不是 Django 原始的 request。

这种架构上的不一致导致在 middleware 中是获取不到 DRF 认证后的用户对象的,因为其认证是在 middleware 之后 view 开始执行的时候。

DRF的权限管理也是在 APIView 类中实现的,可以配置全局的权限设置,但是每个 View 也可以设置自己的权限覆盖全局的设置。

https://www.django-rest-framework.org/api-guide/authentication/

https://www.django-rest-framework.org/api-guide/permissions/

request.user 的延迟计算#

Django 和 DRF 中的 request.user 属性都采用了延迟计算的设计,既只有在对该属性进行访问时才进行认证的计算。

Django中使用了 SimpleLazyObject 来进行延迟计算,代码如下:

# django/contrib/auth/middleware.py
def get_user(request):
    if not hasattr(request, "_cached_user"):
        request._cached_user = auth.get_user(request)
    return request._cached_user


class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        if not hasattr(request, "session"):
            raise ImproperlyConfigured(...)
        request.user = SimpleLazyObject(lambda: get_user(request))

而DRF则使用了缓存变量的做法:

# rest_framework/request.py
@property
def auth(self):
    """
    Returns any non-user authentication information associated with the
    request, such as an authentication token.
    """
    if not hasattr(self, '_auth'):
        with wrap_attributeerrors():
            self._authenticate()
    return self._auth
comments powered by Disqus