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