Django-在处理端点请求之前验证AWS Cognito令牌是否有效
问题描述
所以我有下面的代码,用于检查AWS Cognito令牌。我显然不想将这6行代码添加到每个端点。另外,我不知道这是否是验证的正确方式,我所做的就是期望令牌的格式为‘’,解析它,然后只解码JWT令牌部分。我如何验证每个请求附带的AWS Amplify令牌,以确保用户正确登录。我要将此身份验证添加到APIView终结点和DRF API_VIEW修饰终结点。
views.py
import django.db.utils
from rest_framework import authentication, permissions, status
from rest_framework.views import APIView
from .serializers import *
from .models import *
from rest_framework.response import Response
from django.http import JsonResponse
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from .core.api import jwt
from django.core.exceptions import ObjectDoesNotExist
class LoginView(APIView):
def post(self, request):
# 'Bearer z324weroko2iorjqoi=+3r3+3ij.2o2ij4='
token = request.META['HTTP_AUTHORIZATION'].split(' ')[1]
print(token)
# TODO this should be separated out to a login module
try:
res = jwt.decode_cognito_jwt(token)
return Response(status=status.Http_200_OK)
except:
return Response("Invalid JWT", status=status.HTTP_401_UNAUTHORIZED)
@api_view(['GET'])
@swagger_auto_schema(
operation_description="Get Goals joined by User"
)
def get_goals_by_user(request, user_id):
try:
# Get goal ids of user with id
goals_query = JoinGoal.objects.filter(
joiner_id=user_id).values_list('goal_id', flat=True)
goals_list = list(goals_query)
# Get Goals using list of goals PK with descriptions and uuid
data = list(Goal.objects.filter(
pk__in=goals_list).values('description', 'uuid'))
response_data = dict(goals=data)
return JsonResponse(response_data, status=status.HTTP_200_OK)
except JoinGoal.DoesNotExist:
return Response(dict(error=does_not_exist_msg(JoinGoal.__name__, 'joiner_id', user_id)), status=status.HTTP_400_BAD_REQUEST)
解决方案
如果使用djangorestframework
,来自@bdbd的答案将是您的最佳选择。否则,您可能需要了解以下选项:
- 实现您自己的将执行身份验证的修饰器。这与@login_required修饰符或@user_passes_test修饰符的概念相同。在为基于类的视图编写这样的装饰器时,您可能会对django.utils.decorators.method_decorator感兴趣。
from functools import partial, wraps
from django.utils.decorators import method_decorator
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.
token = request.META['HTTP_AUTHORIZATION'].split(' ')[1]
try:
res = jwt.decode_cognito_jwt(token)
# Authenticate res if valid. Raise exception if not.
except Exception:
# Fail if invalid
return HttpResponseForbidden("You are forbidden here!")
else:
# Proceed with the view if valid
return view_func(request, *args, **kwargs)
return wrapped_view
# We can decorate it here before the class definition but can also be done before the class method itself. See https://docs.djangoproject.com/en/3.2/topics/class-based-views/intro/#decorating-the-class
@method_decorator(
name="post",
decorator=[
cognito_authenticator,
],
)
class SomeView(View):
@method_decorator(cognito_authenticator) # As explained above, this is another way of putting the decorator
def get(self, request):
return HttpResponse("Allowed entry!")
def post(self, request):
return HttpResponse("Allowed entry!")
# Or if using function-based views
@api_view(['POST'])
@cognito_authenticator
def some_view(request):
return HttpResponse(f"Allowed entry!")
- 写一个custom middleware。请注意order很重要。与填充
request.user
字段的默认AuthenticationMiddleware相同。在您的案例中,实现__call__
方法,您将在其中检查Cognito令牌。当令牌无效时,不要通过返回HttpResponseForbidden
继续查看视图,例如,返回此reference。
class CognitoAuthenticatorMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
token = request.META['HTTP_AUTHORIZATION'].split(' ')[1]
try:
res = jwt.decode_cognito_jwt(token)
# Authenticate res if valid. Raise exception if not.
except Exception:
# Fail if invalid
return HttpResponseForbidden("You are forbidden here!")
# Proceed if valid
response = self.get_response(request)
return response
MIDDLEWARE = [
...
'path.to.CognitoAuthenticatorMiddleware',
...
]
更新
以下是使用选项-1运行的示例。为简单起见,settings.py
仅为默认设置。
views.py
from functools import partial, wraps
from django.http import HttpResponse, HttpResponseForbidden
from django.utils.decorators import method_decorator
from django.views import View # If using django views
from rest_framework.views import APIView # If using djangorestframework views
def cognito_authenticator(view_func=None):
if view_func is None:
return partial(cognito_authenticator)
@wraps(view_func)
def wrapped_view(request, *args, **kwargs):
# To simplify the authentication, we would check if there is a query parameter "name=me". If none, it is forbidden.
if request.GET.get('name') == "me":
return view_func(request, *args, **kwargs)
return HttpResponseForbidden("You are forbidden here!")
return wrapped_view
@method_decorator( # Try this style-1
name="get",
decorator=[
cognito_authenticator,
],
)
class SomeView(View): # If using djangorestframework view, this can also inherit from APIView or others e.g. class SomeView(APIView):
@method_decorator(cognito_authenticator) # Or try this style-2
def get(self, request):
return HttpResponse(f"Allowed entry!")
urls.py
from django.urls import path
from my_app import views
urlpatterns = [
path("some-view/", views.SomeView.as_view()),
]
示例运行:
$ curl http://127.0.0.1:8000/my_app/some-view/?name=notme
You are forbidden here!
$ curl http://127.0.0.1:8000/my_app/some-view/?name=me
Allowed entry!
相关文章