DjangoRestFramework认证之自定义TokenAuthentication实现CSRF防御

DRF提供了BasicAuthenticationSessionAuthenticationSessionAuthenticationTokenAuthenticationRemoteUserAuthentication及自定义Authentication,用于API接口的身份验证。其中,SessionAuthentication默认带有CSRF防御,其他验证方法均未做CSRF防御,本文记录如何在其他验证方法中实现CSRF防御

一、Authentication及CSRF防御中间件配置

Authentication配置
为了启用Authentication,需要在项目的settings.py文件中增加REST_FRAMEWORK字段,配置默认验证类DEFAULT_AUTHENTICATION_CLASSES即可,如下:

1
2
3
4
5
6
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
]
}s

上述代码配置当前项目使用SessionAuthentication`TokenAuthentication进行用户验证,authencation按定义的先后顺序调用authenticate(self, request)方法进行验证,如果验证成功,返回元祖(user, auth),分别为request.userrequest.auth;如果验证失败,返回None`,继续下一个验证器进行验证,直到所有的验证器都验证完毕;如果其中某个验证器抛出异常,将导致验证器直接停止。

CSRF防御中间件配置
为了启用CSRF防御,需要在项目的settings.py文件的MIDDLEWARE字段中增加django.middleware.csrf.CsrfViewMiddleware,然后配置CSRF_HEADER_NAME字段,如下:

1
2
3
4
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
]
CSRF_HEADER_NAME = "HTTP_CSRF_TOKEN"

在后续的POST/PUT/DELETE等请求中,通过将cookie中的_csrf_token与header头中的csrf_token进行匹配,进行CSRF验证;CSRF验证失败时,将抛出PermissionDenied异常,返回403状态码,源码如下:

1
2
3
4
5
6
7
8
9
10
11
def enforce_csrf(self, request):
"""
Enforce CSRF validation for session based authentication.
"""
check = CSRFCheck()
# populates request.META['CSRF_COOKIE'], which is used in process_view()
check.process_request(request)
reason = check.process_view(request, None, (), {})
if reason:
# CSRF failed, bail with explicit error message
raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)

CSRF防御的验证应该放在用户身份验证之后,如果用户不存在,则没有验证CSRF防御的必要。

二、重写TokenAuthentication,实现带CSRF的Token验证

在app目录下创建文件authentication.py开发CsrfTokenAuthentication类,继承TokenAuthentication类,重写authenticate方法,在用户验证之后验证CSRFToken是否存在,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author:owefsad
# datetime:2020/12/22 上午12:40
# software: PyCharm
# project: vulscan-webapi
from rest_framework import exceptions
from rest_framework.authentication import TokenAuthentication, CSRFCheck


class CsrfTokenAuthentication(TokenAuthentication):
def authenticate(self, request):
user_auth_tuple = super().authenticate(request)
if user_auth_tuple is not None:
self.enforce_csrf(request)
return user_auth_tuple

def enforce_csrf(self, request):
"""
Enforce CSRF validation for session based authentication.
"""
check = CSRFCheck()
# populates request.META['CSRF_COOKIE'], which is used in process_view()
check.process_request(request)
reason = check.process_view(request, None, (), {})
if reason:
# CSRF failed, bail with explicit error message
raise exceptions.AuthenticationFailed('CSRF Failed: %s' % reason)

配置settings.py文件,使用自定义验证器CsrfTokenAuthentication代替原有的TokenAuthentication,如下:

1
2
3
4
5
6
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'<app>.authentication.CsrfTokenAuthentication',
]
}

启动Django应用即可加载自定义的验证器,实现Token验证防CSRF防御的功能。

owefsad wechat
进击的DevSecOps,持续分享SAST/IAST/RASP的技术原理及甲方落地实践。如果你对 SAST、IAST、RASP方向感兴趣,可以扫描下方二维码关注公众号,获得更及时的内容推送。
0%