본문 바로가기
백엔드/Django

[Django] 접근 제어

by 토마토베이컨수프 2021. 11. 4.

유저 기능이 구현된 프로젝트에서 로그인이 된 유저가 글을 작성할 때, 그 유저에 대한 정보가 글에 함께 저장되도록 합시다. 

그리고 거기에 추가적으로 '로그인을 해야 글을 작성할 수 있고', '내가 작성한 글만 수정, 삭제를 할 수 있도록' 하는 기능을 추가해봅시다.

 

유저가 서버로 보내는 request 처리 과정에서 view에 Mixin이라는 것을 설치하고 이 Mixin에 접근 제어와 관련된 로직들을 추가하면, 로그인이 되어 있지 않은 경우 다른 페이지로 redirect하거나 forbidden response를 돌려주는 등의 일을 수행할 수 있습니다. Mixin은 보통 기존의 클래스에 어떤 기능을 더해줄 때 쓰이는 개념인데, 따라서 뷰 함수가 아닌 뷰 클래스에서만 적용될 수 있습니다.

 

이러한 접근 제어와 관련된 Mixin을 제공하는 패키지 중 django-braces 라는 것을 사용해 보도록 합시다.

해당 패키지는 터미널에서 다운 받으면 됩니다.

pip install django-braces

 


유저 필드 추가

우선 글 작성 모델에 ForeignKey를 이용하여 유저 필드를 추가합니다. 글 작성 모델의 이름은 Post, 유저 모델은 User라고 가정합시다.

# models.py

class User(AbstractUser):
    nickname = models.CharField(max_length=15, unique=True, null=True)
    ...

class Post(models.Model):
    ...
    author = models.ForeignKey(User, on_delete=models.CASCADE)

on_delete=models.CASCADE 는 참조하는 오브젝트가 삭제되면 기존 오브젝트도 자동으로 삭제해 주는 기능을 합니다. 유저 데이터가 삭제되면 해당 유저가 쓴 글들도 모두 자동으로 삭제되는 것이죠.

 

포스트의 유저 인스턴스에 접근하거나 유저 조건으로 포스트들을 필터링하는 방법은 다음과 같이 하면 되겠죠?

data = Post.objects.all().first()
data.author	  # 유저 인스턴스
data.author.nickname	# 유저의 닉네임 접근

# 필터링
Post.objects.filter(author__id=1)
Post.objects.filter(author__nickname='KM')

 


로그인 접근 제어

로그인이 된 유저만 글 작성 페이지로 접근할 수 있고, 로그아웃된 상태에서 글 작성 페이지에 접근하려 한다면 로그인 페이지로 리디렉트 되도록 해봅시다.

 

우선 장고에게 로그인을 위한 url이 뭔지 알려줌으로써 접근 제어에 걸리면 로그인 페이지로 이동하게끔 셋팅해 줍시다.

# settings.py

LOGIN_URL = "account_login"

 

그리고 글 작성 페이지를 랜더링하는 뷰 클래스가 braces패키지의 LoginRequiredMixin를 상속받도록 합니다.

# views.py

from braces.views import LoginRequiredMixin

class PostCreateView(LoginRequiredMixin, CreateView):
    ...

LoginRequiredMixin은 로그인이 되어 있는 유저만 뷰에 접근할 수 있게 해 줍니다. 로그인이 안 되어있으면 로그인 페이지로 리디렉트되고, 로그인을 하면 원래 가려고 하던 페이지로 가게 됩니다.

 


이메일 인증 접근 제어

이런 특정 조건을 가지는 접근 제어를 구현하기 위해선 UserPassesTestMixin을 사용하면 됩니다.

UserPassesTestMixin은 우리가 오버라이딩 할 test_func 함수를 통과하는 유저만 뷰에 접근할 수 있게 해 줍니다.

test_func는 뷰에 접근할 수 있으면 True, 없으면 False를 리턴합니다.

# views.py

from braces.views import LoginRequiredMixin, UserPassesTestMixin
from allauth.account.models import EmailAddress
from allauth.account.utils import send_email_confirmation

def confirmation_required_redirect(self, request):
    send_email_confirmation(request, request.user)
    return redirect("메일 인증 요구하는 페이지의 url name")
    

class PostCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
    ...
    redirect_unauthenticated_users = True
    raise_exception = confirmation_required_redirect
    ...
    
    def test_func(self, user):
        return EmailAddress.objects.filter(user=user, verified=True).exists()

뷰에 접근하지 못하는 유저가 처리되는 방식을 제어하기 위해서는 두 가지 속성을 사용합니다.

  • redirect_unauthenticated_users : 뷰에 접근하지 못하는 유저들 중, 로그인 되어있는 유저와 로그인이 되어 있지 않은 유저를 다르게 처리할 것인지를 정하는 속성입니다. 이걸 True로 하면, 로그인이 안돼있는 유저는 로그인 페이지로 리디렉트되고, 로그인 돼있는 유저는 raise_exception 속성의 값에 따라 처리 방식이 정해집니다. 반대로 이걸 False로 하면, 로그인 상태의 유저, 로그아웃 상태의 유저 모두 raise_exception 속성의 값에 따라 처리됩니다.
  • raise_exception : raise_exception을 True로 설정해 주면 유저가 뷰에 접근할 수 없을 경우 403 Forbidden(권한 없음, 금지됨) 오류가 나고, 커스텀 함수로 설정해 주면 그 함수가 그대로 실행됩니다. 커스텀 함수는 self와 request를 파라미터로 받아야 합니다.

위 뷰 클래스의 경우에는, 로그인이 되어있지 않다면 글 작성 페이지 접근 시 로그인 페이지로 가게 되고, 로그인이 되어있다 하더라도 이메일 인증을 받지 않은 상태라면 이메일 인증을 요구하는 페이지로 리디렉트 되겠지요.

 


본인이 쓴 글만 수정, 삭제

위와 같은 방법으로 본인이 작성한 글만 수정, 삭제할 수 있도록 해봅시다.

class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    ...
    raise_exception = True
    
    def test_func(self, user):
        post = self.get_object()
        return post.author == user

raise_exception을 True로 설정해 주었으니, 유저가 뷰에 접근할 수 없는 경우 403에러가 발생하겠지요. redirect_unauthenticated_users의 기본값은 False이니 따로 설정할 필요는 없습니다.

 

글 삭제를 담당하는 클래스 뷰도 똑같이 따라서 추가해주면 됩니다.

'백엔드 > Django' 카테고리의 다른 글

[Django] allauth 패키지  (0) 2021.10.25
[Django] 제네릭 뷰  (0) 2021.10.11
[Django] 페이지네이션(Pagination) 기능  (0) 2021.10.10
[Django] 폼(Form)에 관하여  (0) 2021.10.05
[Django] MVT 구조  (0) 2021.10.02