Django - Проверка достоверности токена AWS Cognito перед обработкой запроса конечной точки

Так, у меня есть код ниже для проверки токена AWS Cognito. Я явно не хочу добавлять эти 6 строк кода в каждую конечную точку. Также я не знаю, правильный ли это способ проверки, все, что я делаю, это ожидаю, что токен будет иметь формат ' ', разбираю его и просто декодирую часть токена JWT. Как я могу проверить подлинность токена AWS amplify, который приходит с каждым запросом, чтобы убедиться, что пользователь правильно вошел в систему. Могу ли я каким-то образом получить электронную почту пользователя из этого токена?

views.py

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)

Поскольку, похоже, вы используете DRF, вы можете создать свой собственный класс аутентификации и применить обработку JWT там:

from django.contrib.auth.models import AnonymousUser
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions


class MyCustomJWTAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.META['HTTP_AUTHORIZATION'].split(' ')[1]
        try:
            jwt.decode_cognito_jwt(token)
        except Exception:
            raise exceptions.AuthenticationFailed('Invalid JWT')

        return AnonymousUser(), None


class MyCustomAPIView(APIView):
    authentication_classes = (MyCustomJWTAuthentication, )

Или если вы хотите применить его ко всем APIViews:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'path.to.MyCustomJWTAuthentication',
    ),
}

Обратите внимание, что если декодирование JWT не удалось, никакие другие классы аутентификации проверяться не будут. Если вы не хотите этого, измените обработку для пункта except, чтобы не поднимать AuthenticationFailed.

Если вы используете 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. Напишите пользовательское промежуточное ПО. Учтите, что порядок порядка имеет значение. Та же идея, что и у стандартного AuthenticationMiddleware, которое заполняет поле request.user. В вашем случае реализуйте метод __call__, в котором вы будете проверять токены Cognito. Не переходите к представлению, если токен недействителен, возвращая, например, HttpResponseForbidden, как в этом reference.
  2. .
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',
    ...
]

Update

Здесь приведен пример запуска с использованием варианта 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!
Вернуться на верх