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的答案将是您的最佳选择。否则,您可能需要了解以下选项:

  1. 实现您自己的将执行身份验证的修饰器。这与@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!")
  1. 写一个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!

相关文章