# DRF 源码之旅:我曾只用自定义认证器,直到重新发现内置认证器的魅力 ## 1. 我热爱 DRF,但曾不信任它的内置认证器。 {#sec-c2385e3e4312} Django 和 DRF (Django Rest Framework) 是我开发生涯中不可或缺的伙伴。然而,在认证(Authentication)方面,我一直有着自己的“执念”。我通常偏爱 **JWT**、**API Key** 或 **OAuth2** 方案,因为它们是现代应用程序环境下的标准。 奇怪的是,当我翻阅 DRF 官方文档时,发现占据主体的并非我常用的现代认证方式,而是 **Basic、Session、Token、Remote** 等看起来有些“老旧”的认证类。我常常疑惑:“功能强大的 DRF 为何还要内置这些?”因此,我总是习惯性地继承 `BaseAuthentication`,开发并使用自己的自定义认证器。 ## 2. 为何重新审视“内置认证器”的源码? {#sec-331b1362b7e2} 在不断开发自定义认证器的过程中,我突然产生了一个疑问: > “我所构建的认证器,是否真的完美契合 Django 和 DRF 的设计哲学?” 我开始思考,除了简单地将用户对象放入 `request.user` 之外,我是否错过了 DRF 所设计的认证方案中那种“浑然天成”的精妙之处。因此,从今天开始,我将分几篇文章,彻底剖析 DRF 的四种内置认证器: * **BasicAuthentication** (HTTP 基本认证) * **SessionAuthentication** (利用 Django 会话) * **TokenAuthentication** (简单令牌认证) * **RemoteUserAuthentication** (外部认证集成) ## 3. 探寻“不适感”背后隐藏的“哲学” {#sec-e9f76a8db02e} 坦白说,在现代服务中直接使用 `BasicAuthentication` 确实有些让人不舒服。它看起来安全性不高,每次请求都携带用户名和密码的结构也令人不安。然而,深入剖析其源代码,你会发现其中蕴含着关于**“认证失败时如何与客户端沟通”**的精妙设计。 例如,是简单地抛出 `403 Forbidden`,还是通过 `authenticate_header` 引导客户端响应标准的 `401 Unauthorized`。正是这些细节,构成了“Django风格”类的核心。 ## 4. 本系列的目标:让自定义认证器“浑然天成” {#sec-00b6ee52d1d1} 在本系列文章的终点,我希望获得的不仅仅是知识,更是: 1. **启发(Inspiration):** 学习内置认证器为何采用这种结构,领悟其设计哲学。 2. **移植(Porting):** 将这些哲学理念移植到我常用的 JWT 或 OAuth2 自定义认证器中。 3. **和谐(Harmony):** 最终,让我所创建的自定义认证器,在 DRF 的认证体系中运行得如同原生组件一般自然。 这不仅仅是实现功能的开发者,更是迈向能将框架哲学融入代码的开发者之路的第一步。**DRF 认证器源代码分析系列**,现在正式开始! ![개발자가 오래된 자물쇠를 해부하고 있는 이미지](/media/whitedec/blog_img/db4a2af117c3414395e86a0210d28f4a.webp) ## 5. 深入源码:`BasicAuthentication` {#sec-d3e96fecb1fa} 铺垫了这么多,现在让我们正式进入 **BasicAuthentication** 的核心。 --- 打开 DRF 的 `rest_framework/authentication.py`,你就能看到这个类的真面目。它看起来简洁,但其中蕴含着严谨的规则。 ```python class BasicAuthentication(BaseAuthentication): """ HTTP Basic authentication against username/password. """ www_authenticate_realm = 'api' def authenticate(self, request): auth = get_authorization_header(request).split() if not auth or auth[0].lower() != b'basic': return None 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: try: auth_decoded = base64.b64decode(auth[1]).decode('utf-8') except UnicodeDecodeError: auth_decoded = base64.b64decode(auth[1]).decode('latin-1') userid, password = auth_decoded.split(':', 1) except (TypeError, ValueError, UnicodeDecodeError, binascii.Error): msg = _('Invalid basic header. Credentials not correctly base64 encoded.') raise exceptions.AuthenticationFailed(msg) return self.authenticate_credentials(userid, password, request) def authenticate_credentials(self, userid, password, request=None): credentials = { get_user_model().USERNAME_FIELD: userid, 'password': password } user = authenticate(request=request, **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) def authenticate_header(self, request): return 'Basic realm="%s"' % self.www_authenticate_realm ``` ### **为何选择请求头(Header)?** {#sec-d4f681d28784} 我们通常在发送数据时会首先想到 `request.POST` 或 `request.data`。然而,对于认证信息,将其放在**请求头**中是业界惯例。这是为什么呢? 因为认证必须**灵活应对所有 HTTP 方法(GET、POST、PUT、DELETE 等)**。`GET` 请求通常没有 Body,`DELETE` 请求的 Body 也常为空。如果认证信息必须放在 Body 中,那么即使只是查询数据,我们也不得不强行使用 POST 请求,这将导致设计变得非常奇怪。将认证信息放入请求头,就像是约定好**“无论打开哪扇门,都请把身份证拿在手上”**。 --- ### 冷酷的现实:安全漏洞 {#sec-28526421f804} 这种方式最大的缺点是 **Base64**。它仅仅是“编码”而非加密。任何人只需一秒钟,通过在线解码工具就能获取你的用户名和密码。 > **结论:** 在 HTTP 环境下绝对禁止使用。只能在 HTTPS 这种安全的隧道中,并且仅限于非常有限的用途。 --- ### 第一个启发:机器间通信 (M2M) 的高性价比安全 {#sec-999544506ed4} 尽管如此,这种看似过时的认证器也有其魅力之处,那就是在**服务器之间进行通信时**。 HMAC 或复杂的 API Key 系统构建成本较高,但又不想直接裸传数据时,我们可以“巧妙”地调整这种结构: * **自定义加密:** 不使用 Base64,而是传输经过双方服务器才知晓的对称密钥加密的二进制内容。 * **简便实现:** 无需复杂的握手,仅通过 `Basic ` 形式就能实现快速且安全的通信。它成为了“只有我们之间才知道的密码”的高效传输通道。 --- ### **第二个启发:被遗忘的齿轮 `authenticate_header()`** {#sec-8f845e80ab36} 最值得关注的方法是 `authenticate_header()`。在源代码中,你找不到任何直接调用它的地方。然而,它却是 DRF 异常处理器中**生成“401 Unauthorized”响应的核心钥匙**。 在创建自定义认证器时,我们经常会忽略这个方法。实际上,即使缺少这个方法,对**认证(Authentication)**行为本身也没有任何影响。但有必要比较一下,这种微小的差异会使视图层的代码变得多么混乱。 #### **情况 1. 忽略 `authenticate_header` 时(手动方式)** 如果缺少这个方法,DRF 会认为“无法提供认证方式的指导”,因此在认证失败时会抛出 `403 Forbidden` 而非 `401`。为了避免这种情况,视图中需要进行如下“繁琐”的操作: ```Python # ❌ 缺少 authenticate_header 的情况 class MyPostAPIView(APIView): # 使用 IsAuthenticated 会强制返回 403,因此为了 401,我们先允许所有访问。 permission_classes = [AllowAny] def post(self, request): # 必须在视图内部逐一检查认证状态,并手动返回 401。 if not request.user or not request.user.is_authenticated: return Response( {"detail": "로그인이 필요합니다."}, status=status.HTTP_401_UNAUTHORIZED ) # ... 实际业务逻辑开始 ... ``` #### **情况 2. `authenticate_header` 实现时(DRF 哲学的体现)** 相反,如果你的自定义认证器中写下了这短短一行指导信息(`authenticate_header`),DRF 庞大的认证体系就会识别到它,并自动组装出 `401` 响应。 ```Python # ✅ 包含 authenticate_header 的情况 class MyPostAPIView(APIView): # 现在 DRF 会自动抛出 401,因此我们可以声明式地控制权限。 permission_classes = [IsAuthenticated] def post(self, request): # 视图只需专注于“业务逻辑”。 # 认证失败的处理已经在上层(认证器的 authenticate_header 方法)完成了。 ... ``` 这正是**“Django风格”**的美学所在。它避免了将零碎的代码散布在视图中,而是将我们的代码完美地嵌入到框架设计的齿轮之间。通过这一个方法,我们能够清晰地分离“认证”和“授权”的职责,使视图保持轻量。 --- ### DRF 的真正魅力:无声的退让(`None` 的艺术) {#sec-38c90c2a7f2a} 最后,我们再来看看源代码的这部分: ```python if not auth or auth[0].lower() != b'basic': return None ``` 当格式不匹配时,它没有抛出错误,而是**返回 `None` 并悄然退出**。这种微小的处理方式造就了 DRF 的灵活性。正因如此,我们才能在 `AUTHENTICATION_CLASSES` 列表中按顺序放置多个认证器。难道你没有感觉到开发者们“啊,这不是我的方式?那就让下一个认证器来检查吧!”的体贴吗? --- ## 总结 {#sec-e57eed9058e2} `BasicAuthentication` 虽看似老旧,但其结构却是现代认证系统的基石。特别是其异常处理和多重认证支持方式,是我们创建自定义认证器时必须借鉴的宝贵财富。 好了,我们已经翻过了第一座山。在下一篇文章中,我们将深入探讨既熟悉又有些模糊的 **`SessionAuthentication`**。 敬请期待**“你可能不知道的 Session 认证秘密”**!