Published on

view 와 serializer 에서 request user 다루기

Authors

서버에서는 세션이나 토큰등을 이용해 API를 요청한 사람에 대해 구분할 수 있습니다.

비즈니스 로직상 API에서 API를 요청한 유저가 아닌 타 User나 다른 여러 User에게 액션을 주는 상황이라면 User를 별도의 필드로 요청받는 것이 필요합니다.

하지만, 이미 로그인된 유저에게만 해당 API가 사용된다면, 굳이 유저 정보를 입력받을 필요는 없습니다.

이렇게 요청한 유저에 대한 구분이 가능해지면 1)어떤 유저인지 검증하거나 2)permission에 관련한 로직을 만들거나 3)queryset 에 기본적인 필터링 등을 수행 하는 등 다양한 로직을 구현할 수 있습니다.

DRF에서 유저를 가져오는 부분을 매번 작성하기 때문에, 이를 함수로 만들어서 사용하고 있는데 View와 Serializer에서 유저를 가져와 간단한 검증을 함수로 구현한 내용을 기록합니다

기본 Django 인증 개념

1. Django의 인증 시스템

Django는 기본적으로 유저 인증 및 세션 관리를 제공합니다.

인증된 유저는 request.user 속성을 통해 접근할 수 있습니다.

Django의 AuthenticationMiddleware는 요청이 들어올 때마다 세션을 기반으로 유저 객체를 로드합니다.

2. DRF의 인증 클래스

DRF는SessionAuthentication, BasicAuthentication, TokenAuthentication 등의 인증 클래스를 제공합니다.

이 클래스들은 요청의 헤더나 세션에서 인증 정보를 추출하고, 이를 기반으로 request.user를 설정할 수 도 있습니다.

3. 뷰에서 유저 객체 접근

DRF의 View (또는 ViewSet)에서 request.user를 통해 현재 인증된 유저 객체에 접근할 수 있습니다. 이 속성은 인증 클래스에 의해 설정됩니다.

계층별 request user

1. Serializer

Serializer에서 유저 정보를 가져올 때 유저 정보를 가져와 인증된 유저인 경우 attrs에 추가하고 반환하는 방식으로 구현했습니다.


def validate_serializer_request_user(request, attrs: dict) -> object:
    """

    :param request: self
    :param attrs: serializer.attrs
    :return:
    """
    request_user = request.context.get("request").user
    if request_user.is_authenticated:
        attrs["user"] = request_user
        return request_user
    raise AuthenticationFailed({"detail": "Not authenticated"})

이 코드의 주 목적은 request 한 유저가 인증되었는지 검사하는 것 입니다.

인증된 유저라면 attrs에 유저 정보를 추가하고, 인증되지 않았다면 AuthenticationFailed 예외를 발생시킵니다.

1.1 사용 예시와 사례

위에서 말했듯이 로그인 된 상태로 API 요청을 하기 때문에, 어떤 유저인지 서버는 이미 알고있습니다.

따라서 validate_serializer_request_user를 사용하는 경우는 다음과 같습니다:

  • MainModel에 연결된 User가 요청한 유저와 일치하는 경우
    • ex) 본인이 요청한 게시글 생성 요청에서, 본인이라는 정보를 request_body에 포함하지 않아도 요청이 처리될 수 있습니다.

class MainModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = MainModel
        fields = [
            "id",
            ...(생략)
            ]

    def validate(self, attrs):
        attrs = super().validate(attrs)
        validate_serializer_request_user(self, attrs)
        return attrs

     @transaction.atomic
    def create(self, validated_data):
        validated_data["some_user_id"] = validated_data.pop("user").id
        instance = super().create(validated_data)
        return instance

1.2 + NestedSerializer 와 Foreign Key 필드 검증

user / auth 와 상관 없지만, NestedSerializer를 사용이 빈번해지면서, 데이터 get 혹은 pop 으로 가져와서 사용할 수 밖에 없는 고충이 있었습니다.

이는 NestedSerializer를 사용하면서 생기는 현상으로, Serializer에서 외래 키 필드를 검증하는 경우가 많아졌을때 유용하게 사용할 수 있을 것 같습니다.

아래 함수는 serailzer 에서 반복되는 foreign key model에 대한 validation을 수행합니다.

validate_foreignkey_model(attrs, "some_fk_field", SomeModel) 로 동작하며, MainModel 이 some_fk_field를 NestedSerializer로 사용하면서 겪는 Validation 을 수행할 수 있습니다.


def validate_foreignkey_model(attrs, field: str, model) -> object:
    """

    :param attrs:
    :param field:
    :param model:
    :return:
    """
    obj = attrs.pop(field)
    model_obj = None
    if obj:
        try:
            model_obj = model.objects.get(id=obj["id"])
            attrs[f"{field}_id"] = model_obj.id
        except model.DoesNotExist:
            raise ValidationError({field: [f"존재하지 않는 {field} 입니다."]})

    return model_obj

아래 구체적인 예시를 통해 보겠습니다.

some_fk_field 가 MainModel의 어떤 FK 필드 이고, 이것을 NestedSerializer로 사용합니다.

이럴때, some_fk_field 객체를 사용하면


class MainModelSomeModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = SomeModel
        fields = [
            "id",
            "name",
        ]
        read_only_fields = [
            "name",
        ]

class MainModelSerializer(serializers.ModelSerializer):
	some_fk_field = MainModelSomeModelSerializer()

    class Meta:
        model = MainModel
        fields = [
            "id",
            "some_fk_field",
            ...(생략)
            ]

    def validate(self, attrs):
        attrs = super().validate(attrs)
        validate_foreignkey_model(attrs, "some_fk_field", SomeModel)
        return attrs

2. View

이 함수는 익명 유저인지 확인하고, 익명일 경우 NotAuthenticated 예외를 발생하는 함수로 만들었습니다.


def validate_view_request_user(request) -> object:
    """

    :param request: self.request
    :return:
    """
    user = request.user
    if user.is_anonymous:
        raise NotAuthenticated()
    return user

이 함수는 request 객체에서 유저 정보를 가져와 익명 여부를 확인합니다. 익명 유저라면 NotAuthenticated 예외를 발생시키며, 인증된 유저라면 해당 유저 객체를 반환합니다.

이 함수를 통해 self.request내부에 있는 유저객체를 이용해 View단의 get_queryset에서 전체 [GET]API에 대한 로직에서 유저기반으로 필터링 할 수 있는 것 입니다.

2.1 예시


    def get_queryset(self):
        queryset = super().get_queryset().filter(user=validate_view_request_user(self.request))
        return queryset

3. Permission

DRF의 BasePermission 클래스를 통해 View단에서 어떤 API 에 Permission 을 적용할 것인지 설정할 수 있고 소스코드는 아래와 같습니다. 각 permission 함수에서 True 판정이 되면 권한에 대한 액션을 허용합니다.

class BasePermission(metaclass=BasePermissionMetaclass):
    """
    A base class from which all permission classes should inherit.
    """

    def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

    def has_object_permission(self, request, view, obj):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

BasePermission 을 상속받아 아래와 같은 커스텀 Permission 을 사용할 수 있습니다.

request.user 객체를 가져올 수 있기 때문에, user 와 관련된 flag를 이용해 API를 통제할 수 있습니다.


class SomePermission(permissions.BasePermission):
    def has_permission(self, request, view):
        return request.user.is_authenticated and request.user.some_flag # 이렇게 다른 조건이나 상태를 추가

hongreat 블로그의 글을 봐주셔서 감사합니다!