터칭 데이터

장고(Django) User 권한 관리 본문

장고 (Django)

장고(Django) User 권한 관리

터칭 데이터 2023. 11. 5. 15:46

 

 

 

 

각 질문(question)마다 작성자 표시하기

 

 

로그인한 사용자가 Question을 만들 때 사용자가 만든 질문이라는 것을 표현해보겠습니다.

 

 

 

Question List 페이지에서 각 질문마다 질문을 작성한 사용자 필드 owner라 표시되도록 할겁니다.

 

 

 

 

 

 

 

 

 

 

Question List 페이지에 로그인&로그아웃 기능 추가

 

먼저 Question List에서 로그인하고 로그아웃할 수 있는 기능을 더할겁니다.

 

 

polls_api/urls.py

 

rest/api-auth 패턴을 path로 추가합니다.

 

Django Rest Framework의 `rest_framework.urls` 패턴은 Django에서 제공하는 기본 로그인 및 로그아웃 페이지를 포함하는 URL 패턴입니다. 이 패턴을 사용하면 API에 로그인 및 로그아웃 기능을 제공할 때 편리하게 사용할 수 있습니다.

 

 


이 URL 패턴을 사용하려면 다음 단계를 따르면 됩니다:

1. `rest_framework.urls` 패턴을 URL 설정 파일(`urls.py`)에 추가합니다. 이 패턴은 API에 로그인과 로그아웃 기능을 포함하는 URL을 생성합니다.

2. 패턴을 특정 URL 경로에 추가하여 해당 경로로 요청이 오면 로그인 및 로그아웃 페이지로 이동하도록 설정합니다. 이를 위해 Django의 `include` 함수를 사용합니다.

예를 들어, 아래와 같이 `urls.py` 파일에 패턴을 추가할 수 있습니다:

from django.urls import path, include

urlpatterns = [
    # 다른 URL 패턴들을 여기에 추가

    # /api-auth/ 경로로 들어오는 요청은 rest_framework.urls 패턴을 사용
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]



위의 코드에서:

- `'api-auth/'`는 클라이언트가 로그인 및 로그아웃 페이지로 이동할 때 사용할 경로입니다. 이 경로를 사용자 정의할 수 있습니다.

- `include('rest_framework.urls', namespace='rest_framework')`은 `rest_framework.urls` 패턴을 추가합니다. `namespace`는 URL 패턴의 이름 공간을 지정하며, 이를 통해 URL 패턴이 충돌하지 않도록 구분할 수 있습니다.

이렇게 설정하면 `/api-auth/login/` 경로로 이동하면 로그인 페이지가 표시되고, `/api-auth/logout/` 경로로 이동하면 로그아웃 페이지가 표시됩니다. 이 페이지들은 기본적으로 Django Rest Framework에서 제공하는 것으로, 사용자가 로그인하거나 로그아웃할 수 있도록 합니다.

 

 

 

 

 

 

 

mysite/settings.py에서

 

위와 같이 코드를 작성해줍니다.

 

Django 프로젝트의 `settings.py` 파일에서 `LOGIN_REDIRECT_URL` 및 `LOGOUT_REDIRECT_URL` 설정은 사용자가 로그인 또는 로그아웃 후에 어떤 페이지로 리디렉션될지를 지정하는 역할을 합니다.

1. `LOGIN_REDIRECT_URL`:
   - 이 설정은 사용자가 로그인에 성공한 후 리디렉션될 URL을 지정합니다. 즉, 사용자가 로그인한 후, 로그인 성공 후에 표시될 페이지를 결정합니다.
   - 예를 들어, 사용자가 로그인하면 특정 페이지로 리디렉션되도록 설정할 수 있으며, 이를 통해 사용자가 로그인한 후에 특정 기능이나 대시보드 페이지로 이동하도록 할 수 있습니다.

2. `LOGOUT_REDIRECT_URL`:
   - 이 설정은 사용자가 로그아웃한 후에 리디렉션될 URL을 지정합니다. 즉, 사용자가 로그아웃한 후, 로그아웃 후에 표시될 페이지를 결정합니다.
   - 예를 들어, 사용자가 로그아웃하면 로그아웃 후에 홈페이지 또는 다른 곳으로 리디렉션되도록 설정할 수 있습니다.

이러한 설정을 통해 사용자 경험을 개선하고, 로그인 및 로그아웃 후에 사용자가 보고자 하는 페이지로 자동으로 이동할 수 있습니다.

 

 

 

 

 

 

브라우저에서 확인해보면

 

 

우측 상단에 Log in 버튼이 생겼습니다. 혹시 어드민 페이지에서 로그인일 이미 하셔서 admin이라고 뜬다면 클릭 뒤 로그아웃하시면 됩니다.

 

 

 

 

 

admin 사용자가 아닌 일반 유저로 로그인 해주세요.

 

 

 

 

아까의 우측상단의 Login 자리에 현재 로그인한 사용자의 이름이 뜨고 클릭하면 Log out이 표시됩니다. 이를 클릭해 정상작동되었다면 우리는 로그인&로그아웃 기능을 구현한 것입니다.

 

 

 

 

그런데 Question List 최하단부에 가면

 Question text로 질문을 새로 입력할 수는 있는데 질문 작성자인 Owner를 고를 수 있습니다.

고를 수 있으면 안됩니다! 현재 우리가 로그인한 user1으로만 작성할 수 있어야 합니다.

이를 해결해봅시다.

 

 

 

 

 

 

Owner 선택창 없애기

 

polls_api/serializers.py

 

ReadOnlyField로 owner 필드를 읽을 수만 있게 설정해줬습니다.

 

ReadOnlyField에 대한 자세한 살명은 아래와 같습니다.

 

Django의 serializers 모듈에서 제공하는 `ReadOnlyField`는 읽기 전용 필드로, 일반적으로 직렬화 작업을 수행할 때 특정 모델 필드의 값을 읽기만 가능하도록 만드는 데 사용됩니다. 이 필드를 사용하면 해당 필드의 값을 클라이언트에게 제공할 수 있지만 클라이언트가 해당 필드를 업데이트할 수는 없습니다.

`ReadOnlyField`의 주요 속성 중 하나는 `source`입니다. `source`는 이 필드가 직렬화할 데이터의 소스를 지정하는 데 사용됩니다. 즉, 어떤 모델 필드 또는 속성에서 데이터를 가져올 것인지를 나타냅니다. 

예를 들어, 다음은 `ReadOnlyField`를 사용하여 `UserProfile` 모델의 `full_name` 필드 값을 직렬화하는 예제입니다:

from rest_framework import serializers

class UserProfileSerializer(serializers.Serializer):
    full_name = serializers.ReadOnlyField(source='get_full_name')

    class Meta:
        model = UserProfile
        fields = ['full_name']


위의 코드에서 `ReadOnlyField`를 사용하여 `full_name` 필드를 생성하고, `source` 속성을 사용하여 데이터를 어디에서 가져올 것인지를 `get_full_name` 메서드로 지정합니다. 이로써 클라이언트에게 `full_name` 필드의 값을 제공하며, 이 값은 읽기 전용이므로 클라이언트가 업데이트할 수 없습니다.

`ReadOnlyField`와 `source`를 사용하면 모델 데이터를 직렬화하고 원하는 방식으로 클라이언트에게 제공할 수 있습니다.

 

 

 

 

 

브라우저를 새로고침 해보면

 

Owner를 선택하던 영역이 사라졌습니다.

 

그런데 또 여기서 끝이 아닙니다. Question text 입력란에 질문을 입력하고 POST를 눌러도 모델에 반영되지는 않습니다. 따로 구현해주어야 합니다.

 

 

 

 

 

 

 

 

 

 

 

입력한 Question text가 모델에 반영되도록 하기

 

polls_api/views.py

 

perform_create를 구현했는데요.

 

Django Rest Framework의 generics 뷰에서 perform_create 메서드는 새로운 객체를 생성하고 저장하기 전에 추가적인 동작을 수행하는 데 사용됩니다. 일반적으로 이 메서드를 사용하여 POST 요청으로 들어온 데이터를 기반으로 모델 객체를 생성하고 저장합니다. perform_create 메서드를 사용하여 객체 생성 전에 사용자 지정 로직을 수행할 수 있습니다.

 

perform_create 메서드에서 owner 속성은 일반적으로 사용자가 어떤 모델 객체를 만들 때, 그 모델 객체가 어떤 사용자(또는 소유자)와 연결되어야 하는지를 나타내는데 사용됩니다. 이 owner 속성은 주로 모델 간의 관계를 설정하고 객체를 생성하는 동안 해당 모델 필드를 설정하기 위해 사용됩니다.

예를 들어, 게시물(Post) 모델을 가정해보겠습니다. 게시물은 특정 사용자(User)에 의해 작성되었을 수 있으며, 게시물을 만들 때 사용자를 소유자(owner)로 지정해야 합니다. 이런 경우 perform_create 메서드에서 owner 속성을 설정하고 새로운 게시물을 생성할 때 게시물의 소유자를 지정할 수 있습니다.

 

 

 

 

 

브라우저에서 잘 작동하나 확인해볼까요?

POST를 누르면

 

 

 

 

status code로 HTTP 201 Created가 뜨고

 

질문 작성자인 owner 필드까지 표시된 JSON 형태의 Question 객체가 잘 표시되는 것을 확인할 수 있습니다.

 

 

 

 

 

 

 

 

로그아웃한 상태에서는 질문 입력을 못하게 만들기

 

 

그런데 로그아웃한 상태에서 질문을 입력하면 어떻게 될까요?

로그아웃을 하고 질문을 입력하면

 

 

 

 

 

 

 

이러한 에러 메시지는 Django 애플리케이션에서 "Question.owner" 필드에 사용자(User) 객체가 필요한데 사용자가 로그아웃한 상태에서 해당 필드에 접근하려고 시도할 때 발생할 수 있습니다. "Question.owner" 필드는 외래 키(ForeignKey)로 사용자(User) 모델과 연결된 필드로, 사용자를 가리켜야 합니다.

 

당장 이해하기 쉽게 얘기하면 로그아웃한 상태에서는 Question을 입력할 수 없도록 만들면 된다는 것입니다.

 

 

 

 

 

 

polls_api/views.py

 

permission_classes 변수는 API 뷰에 대한 권한 제어를 설정하는 데 사용됩니다. 이 변수는 해당 API 뷰에 접근할 때 사용자에게 필요한 권한 또는 권한 클래스를 지정합니다.

 

또 permissions.IsAuthenticatedOrReadOnly 클래스는 API 뷰에서 사용자가 인증되었을 경우에는 모든 HTTP 요청을 허용하고, 인증되지 않은 경우에는 읽기 전용 요청(GET, HEAD, OPTIONS)만 허용하는 권한 클래스입니다.

 

 

 

 

 

 

 

 

로그아웃 상태에서는 상세 페이지에서 수정과 삭제 권한 없애기

 

 

우리는 rest/question/question의id URL로 특정 questioin의 상세 페이지에 방문했습니다.

 

그런에 우측상단을 보면 분명 로그아웃된 상태인데도 수정(PUT), 삭제(DELETE)가 가능하고 버튼과 폼으로 구현되어 있습니다. 로그아웃 한 사람은 question의 수정과 삭제를 할 수 없도록 이를 손보겠습니다.

 

 

 

 

polls_api/views.py

 

뷰에서 QuestionDetail 클래스에 QuestionList 때와 마찬가지로 permission_classes를 지정합니다.

 

 

 

 

브라우저의 Question Detail의 페이지를 새로고침하면

수정폼도 삭제 버튼도 사라졌습니다.

 

 

 

 

 

 

 

로그인한 본인만 수정과 삭제할 수 있도록 하기

 

그런데 문제가 또 있습니다.

 

id 10인 질문은 user1이 작성했으므로 user1만 수정 및 삭제가 가능해야하는데

 

 

우측 상단을 보시면 user3로 로그인했음에도 user1이 작성한 질문을 수정하고 삭제할 수 있습니다.

로그인한 본인이 본인의 질문만 수정하고 삭제할 수 있도록 하겠습니다.

 

 

 

그러기 위해서는 저희가 polls_api/views.py에서 작성했던 것처럼 기본적으로 제공되는 permissioins만 사용하는 것이 아닌

 

별도의 permission을 구현해줘야 합니다.

 

 

 

 

polls_api/permsiions.py

 

polls_api 폴더에 permssions.py를 만들어 별도의 permissions를 구현하겠습니다.

 

permissions.BasePermission을 상속받아 사용자 본인이 아니라면 읽기만 가능한 permission 클래스를 구현합니다.

 

`if request.method in permissions.SAFE_METHODS:`는 RESTful API에서 "안전한 메서드(SAFE_METHODS)"를 확인하고, 이러한 메서드에 대해서는 항상 권한을 허용하는 조건을 검사하는 부분입니다.

RESTful API에서 "안전한 메서드"란 데이터를 변경하지 않고 읽기만 하는 HTTP 메서드를 의미합니다. 주로 다음과 같은 HTTP 메서드가 안전한 메서드로 간주됩니다:

- GET: 데이터 조회
- HEAD: 헤더 정보 조회 (실제 데이터를 반환하지 않음)
- OPTIONS: 서버에서 제공하는 메서드 목록을 조회

이러한 안전한 메서드는 일반적으로 읽기 작업을 수행하고, 데이터 변경을 수반하지 않기 때문에 모든 사용자에 대한 읽기 권한을 부여하는 것이 보편적입니다. 따라서 `if request.method in permissions.SAFE_METHODS:` 조건은 HTTP 요청 메서드가 "안전한 메서드" 중 하나인 경우 항상 `True`를 반환하여 모든 사용자에게 읽기 권한을 부여합니다.

그러나, 안전한 메서드가 아닌 쓰기 작업 (예: POST, PUT, DELETE)을 수행하려는 경우 `return obj.owner == request.user` 조건을 검사하여 해당 객체(obj)의 소유자(owner)와 현재 요청을 보낸 사용자(request.user)가 일치하는지 확인합니다. 일치하지 않을 경우 권한이 거부됩니다.

이렇게 함으로써 읽기 작업은 모든 사용자에게 허용되고, 쓰기 작업은 해당 객체의 소유자에게만 허용되도록 권한을 제어할 수 있습니다.

 

 

 

 

 

구현을 끝낸 뒤 polls_api/views.py에서

# (..생략..)
from .permissions import IsOwnerOrReadOnly
# (..생략..)
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]

 

우리가 만든 IsOwnerOrReadonly를 permission_classes에 추가합니다.

 

 

 

 

 

브라우저에서 살펴보면

 

 

우리가 의도한대로 user3로 로그인해서는 user1이 작성한 question의 상세페이지에서 수정과 삭제가 불가능합니다.

 

 

 

 

 

 

반면 user1으로 로그인하면 본인이 작성한 question이므로 수정과 삭제가 가능합니다.