python - Django - 除非另有说明,否则自动将身份验证器添加到每个端点
问题描述
所以我创建了这个自定义身份验证器,并且我有超过 30 个端点。对于除 3 个端点之外的所有端点,它都需要身份验证。所以我几乎添加@custom_authenticator
到每个函数或类@method_decorator(custom_authenticator)
的情况下APIView
。有没有办法可以自动将其添加到端点并添加一个装饰器来关闭特定端点功能的身份验证?例如
@donotauth
def endpoint(request)
然后endpoint()
不会先运行身份验证器。理想情况下,该解决方案应与下面的自定义身份验证器一起使用
自定义身份验证器
def cognito_authenticator(view_func=None):
if view_func is None:
return partial(cognito_authenticator)
@wraps(view_func)
def wrapped_view(request, *args, **kwargs):
# Check the cognito token from the request.
auth = request.headers.get("Authorization", None)
if not auth:
return Response(dict(error='Authorization header expected'), status=status.HTTP_401_UNAUTHORIZED)
parts = auth.split()
if parts[0].lower() != "bearer":
return Response(dict(error='Authorization header must start with bearer'),
status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) == 1:
return Response(dict(error='Token not found'), status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) > 2:
return Response(dict(error='Authorization header must be Bearer token'),
status=status.HTTP_401_UNAUTHORIZED)
token = parts[1]
try:
res = decode_cognito_jwt(token)
expiration = datetime.utcfromtimestamp(res['exp'])
current_utc = datetime.utcnow()
if current_utc > expiration:
return Response(dict(error=f'current time:{current_utc} is after expiration:{expiration}',
user_msg='Please login again'), status=status.HTTP_400_BAD_REQUEST)
except Exception:
# Fail if invalid
return Response(dict(error="Invalid JWT"),
status=status.HTTP_401_UNAUTHORIZED) # Or HttpResponseForbidden()
else:
# Proceed with the view if valid
return view_func(request, *args, **kwargs)
return wrapped_view
使用中间件的解决方案 1:
我尝试添加中间件,但它会引发任何带有@api_view
装饰器的错误。我得到的错误是AssertionError: .accepted_renderer not set on Response.
如何在每个端点上设置我的自定义身份验证,无论它是否具有@api_view
装饰器或APIView
. 最终目标应该是自动将上述内容添加cognito_authenticator
到任何端点,并指定何时不使用身份验证器(可能是功能性装饰器)
视图.py
@api_view(['GET'])
@swagger_auto_schema(
operation_description="Get <count> most recent posts by category"
)
def get_most_recent_posts_by_category(request, category, count):
return Response(status=status.HTTP_200_OK)
中间件
from datetime import datetime
from rest_framework import status
from rest_framework.response import Response
from cheers.core.api.jwt_helpers import decode_cognito_jwt
class CognitoMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_view(self, request, view_func, view_args, view_kwargs):
auth = request.headers.get("Authorization", None)
if not auth:
return Response(dict(error='Authorization header expected'), status=status.HTTP_401_UNAUTHORIZED)
parts = auth.split()
if parts[0].lower() != "bearer":
return Response(dict(error='Authorization header must start with bearer'),
status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) == 1:
return Response(dict(error='Token not found'), status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) > 2:
return Response(dict(error='Authorization header must be Bearer token'),
status=status.HTTP_401_UNAUTHORIZED)
token = parts[1]
try:
res = decode_cognito_jwt(token)
expiration = datetime.utcfromtimestamp(res['exp'])
current_utc = datetime.utcnow()
if current_utc > expiration:
return Response(dict(error=f'current time:{current_utc} is after expiration:{expiration}',
user_msg='Please login again'), status=status.HTTP_400_BAD_REQUEST)
except Exception:
# Fail if invalid
return Response(dict(error="Invalid JWT"),
status=status.HTTP_401_UNAUTHORIZED) # Or HttpResponseForbidden()
else:
# Proceed with the view if valid
return None
设置.py
MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'cheers.middleware.CognitoMiddleware.CognitoMiddleware'
]
解决方案 2 使用身份验证器
验证器.py
class CognitoAuthentication(BaseAuthentication):
def authenticate(self, request):
auth = request.headers.get("Authorization", None)
if not auth:
return Response(dict(error='Authorization header expected'), status=status.HTTP_401_UNAUTHORIZED)
parts = auth.split()
if parts[0].lower() != "bearer":
return Response(dict(error='Authorization header must start with bearer'),
status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) == 1:
return Response(dict(error='Token not found'), status=status.HTTP_401_UNAUTHORIZED)
elif len(parts) > 2:
return Response(dict(error='Authorization header must be Bearer token'),
status=status.HTTP_401_UNAUTHORIZED)
token = parts[1]
try:
res = decode_cognito_jwt(token)
expiration = datetime.utcfromtimestamp(res['exp'])
current_utc = datetime.utcnow()
if current_utc > expiration:
return Response(dict(error=f'current time:{current_utc} is after expiration:{expiration}',
user_msg='Please login again'), status=status.HTTP_400_BAD_REQUEST)
except Exception:
# Fail if invalid
return Response(dict(error="Invalid JWT"),
status=status.HTTP_401_UNAUTHORIZED) # Or HttpResponseForbidden()
else:
# Proceed with the view if valid
return AnonymousUser(), None
设置.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'cheers.utils.authenticator.CognitoAuthentication',
),
}
但是在APIView
发布功能上它给出了错误django.template.response.ContentNotRenderedError: The response content must be rendered before it can be iterated over.
解决方案
要在 DRF API 端点中添加不同级别的身份验证,您可以使用项目级别身份验证和视图级别身份验证:
# config/settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.AllowAny', to allow all
# 'rest_framework.permissions.IsAdminUser', only admin
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly', only authenticated can write
'rest_framework.permissions.IsAuthenticated',
]
}
# app_name/views.py
@permission_classes((IsAdminUser, ))
def example_view(request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
在这种情况下,所有端点都受IsAuthenticated
权限保护。但是视图example_view
会覆盖全局权限并IsAdminUser
改为使用。
推荐阅读
- html - 单击具有跨度类的按钮
- php - 如何加入多个表,其中一些(不是所有)列相同并返回每列的总和?
- javascript - 如何通过按散点图中的更新按钮来更改(过渡)每个圆的半径?
- mysql - 我如何破解这个 SQL Soccer Matches 作业?
- python - string.find 和 list.index 的 python 方法命名背后的基本原理
- neo4j - 如何为节点设置列表属性?
- async-await - 如何在 Svelte 的 {#await} 中使用 bind:this 时触发反应?
- openssl - 如何查看证书是否为 SHA384?
- c# - 如何使用 if 语句 MQL5 更新变量
- python-3.x - 如何使用请求库将 shell 脚本中的 curl 命令转换为 python