我为我的 django Rest api 创建了一些自定义中间件,以强制设置自定义标头的 CSRF 安全策略。我对 CsrfViewMiddleware 进行了子类化并重写了一些方法来满足我的需求,最值得注意的是 check_token 方法。然而,我后来注意到所有请求都将属性“csrf_exempt”设置为 true,即使我没有使用 csrf_exempt 装饰器。我搜索了 django 文档,试图找出为什么会发生这种情况,但没有成功。这是我的自定义中间件:
class CustomCsrfMiddleware(CsrfViewMiddleware):
def _reject(self, request, reason):
logger.warning("Forbidden (%s): %s", reason, request.path)
return Response(
{'detail': 'Forbidden', 'reason': reason},
status.HTTP_403_FORBIDDEN
)
def _check_token(self, request):
# Get the two relevant pieces of information, the
# the returned csrftoken in the custom header and the hmac
try:
hmac = _get_hmac(request)
except BadDigest as e:
raise RejectRequest(e.reason)
try:
request_csrf_token = request.META[settings.CSRF_HEADER_NAME]
token_source = settings.CSRF_HEADER_NAME
except KeyError:
raise RejectRequest(REASON_CSRF_TOKEN_MISSING)
try:
_check_token_format(request_csrf_token)
except InvalidTokenFormat as e:
reason = self._bad_token_message(e.reason, token_source)
raise RejectRequest(reason)
if not _does_match(request_csrf_token, hmac):
reason = self._bad_token_message("incorrect", token_source)
raise RejectRequest(reason)
def process_request(self, request):
try:
csrf_secret = self._get_secret(request)
if csrf_secret is None:
raise InvalidTokenFormat(REASON_CSRF_TOKEN_MISSING)
except InvalidTokenFormat:
_add_new_csrf_cookie(request)
else:
if csrf_secret is not None:
request.META["CSRF_COOKIE"] = csrf_secret
def process_view(self, request, callback, callback_args, callback_kwargs):
# exempt /hook paths
print('callback: ', getattr(callback, "csrf_exempt", False))
return super().process_view(request, callback, callback_args, callback_kwargs)
还有我的 CSRF 设置:
CSRF_COOKIE_NAME = 'csrftoken'
CSRF_COOKIE_AGE = 60 * 60 * 24 * 30 # 1 month
CSRF_SAMESITE = 'lax'
CSRF_COOKIE_SECURE = True
CSRF_TRUSTED_ORIGINS = ['https://localhost:3000']
CSRF_COOKIE_HTTPONLY = False
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
CSRF_COOKIE_DOMAIN = 'localhost'
我发现了问题。事实证明,django Rest框架将所有视图包装在APIView的as_view方法的返回语句中的csrf_exempt中。因此,我的解决方案是创建另一个名为 csrf_ignore 的装饰器,在名为 csrf_ignore 的视图上设置一个新属性。
from functools import wraps
def csrf_ignore(view_func):
"""Mark a view function as being exempt from the CSRF view protection."""
# view_func.csrf_exempt = True would also work, but decorators are nicer
# if they don't have side effects, so return a new function.
@wraps(view_func)
def wrapper_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapper_view.csrf_ignore = True
return wrapper_view
然后在我的中间件的process_view函数中添加一些逻辑:
def process_view(self, request, callback, callback_args, callback_kwargs):
if not getattr(callback, 'csrf_ignore', False):
setattr(callback, 'csrf_exempt', False)
return super().process_view(request, callback, callback_args, callback_kwargs)
现在正确的属性已设置,中间件将在需要时检查 csrf 保护。