[Django] @login_required VS is_authenticated

한결·2023년 4월 11일
0

WEB

목록 보기
21/63
post-thumbnail

Django에는 로그인 상태가 아니면 기능을 할 수 없게하는 방법 2가지가 있음

  1. 데코레이터 @login_required 사용
  2. is_authenticated 함수 사용

이 두개가 완전히 같은 기능을 하고 같은 용도로 쓰이는지 궁금해짐

결론
1,2 는 비슷한 기능을 함
하지만 요청을 어떻게 정의하냐에 따라, @login_required 가 구조적으로 처리하지 못하는 요청이 존재할 수 있다.
따라서, 요청에 따라 적절히 사용해야함

@login_required 에러 상황

게시글 작성 or 삭제 요청을 처리하는 상황

게시글의 데이터에는 다음과 같이 user 정보가 들어간다

따라서 로그인 상태에서만 게시글을 쓰고 지울 수 있게 해야한다

이를 위해서

# articles/views.py
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST, require_GET

@login_required
@require_POST
def delete(request, pk):
    article = Article.objects.get(pk=pk)
    if request.user == article.user:
        article.delete()
        return redirect('articles:index')
    return redirect('articles:detail', article.pk)

데코레이터 @login_required 를 사용해서 구현하려 했다

처음 생각은 @login_required를 import 해서 로그인이 필요한 기능(게시글 작성, 삭제 등) 앞에 붙여주기만 하면

  1. 로그인을 하지 않으면 기능을 수행할 수 없도록 하는 기능을 간편하게 더해줄 수 있고

  2. 로그인상태가 아니면 로그인 페이지로 redirect해주기 때문에 매우 간편하게 구현할 수 있다고 생각했다

plus
네이버, 구글 같은 실제 홈페이지들을 생각해보면
1. 로그인이 필요한 기능을 클릭했을 때
2. 로그인 페이지로 넘어가고
3. 로그인 후 메인 페이지가 아닌 기존에 있던 페이지로 넘어가게 해준다.

이를 구현하기 위해 next parameter 사용

또한,
이를 위해서 login.html을 수정해줘야 한다
next를 사용하기 전엔

{% block content %}
  <h1>Login</h1>
  <form action="{% url 'accounts:login' %}" method="POST">
    {% csrf_token %}
    {{form.as_p}}
    <input type="submit" value="로그인">
  </form>
{% endblock content %}

위처럼 url을 지정 해줬지만
next를 사용하려면 url을 비워줘야한다

http://127.0.0.1:8000/accounts/login/?next=/articles/create/

위처럼 next 유무에 따라AuthenticationForm이 알아서
next가 있으면 거기로 보내고 없으면
return redirect(request.GET.get('next') or 'articles:index')
에 써있는 것 처럼 'articles:index'redirect 해주기 때문이다

{% extends 'base.html' %}

{% block content %}
  <h1>Login</h1>
  <form action="" method="POST">
    {% csrf_token %}
    {{form.as_p}}
    <input type="submit" value="로그인">
  </form>
{% endblock content %}

@login_require로 구현하려 했던 흐름

  1. 게시글 삭제(or 작성)클릭
  2. 로그인 필요함 (작성자만 삭제할 수 있기 때문)
  3. 로그인 페이지로 redirect
  4. 로그인
  5. 로그인 후 다시 게시글 삭제 페이지로 redirect
  6. 삭제 완료

발생한 에러

  1. 로그인 안한 상태의 메인페이지

  2. 비로그인 상태로 detail 페이지에서 게시글 삭제 시도

  3. delete view 함수의 @login_required로 인해 로그인 페이지로 리다이렉트 http://127.0.0.1:8000/accounts/login/?next=/articles/1/delete/

  1. redirect로 이동한 로그인 페이지에서 로그인 진행

  2. next 라는 쿼리파라미터에 담긴 /articles/1/delete/GET 방식으로 요청 (redirect)

  • 삭제 요청
  • 새 게시물 작성 요청

원인

일단 articles/views.pycreate, delete 함수를 살펴보자

@login_required
@require_POST
def create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST, request.FILES)
        if form.is_valid():
            article = form.save(commit=False)
            article.user = request.user
            article.save()
            return redirect('articles:detail', article.pk)
    else:
        form = ArticleForm()

    context = {'form': form}
    return render(request, 'articles/create.html', context)

@login_required
@require_POST
def delete(request, pk):
    article = Article.objects.get(pk=pk)
    if request.user == article.user:
        article.delete()
        return redirect('articles:index')
    return redirect('articles:detail', article.pk)

위처럼 데코레이터 @require_POST를 사용했다.

공식문서를 살펴보면

POST method로 들어오는 것만 처리해준다고 한다

근데 아까의 흐름을 떠올리면
1. 비로그인 상태
2. 로그인 필요한 기능 수행하려함
3. @login_required 가 로그인 페이지로 redirect
4. 로그인
5. next에 담긴 곳으로 GET 방식으로 redirect

즉, 게시글 삭제, 작성의 기능은 @require_POST를 사용해 POST method만 받도록 해놓았는데 @login_required 이후 GET method로 요청이 들어오니 405상태 코드 에러를 받게 된것이다

결론

@login_required 는 정의되는 요청에 따라 적절하게 사용해야한다 (무지성 사용 금지)

@require_POST를 사용하면서 위 문제를
if request.user is_authenticated: 사용해서 해결해보자

해결

CREATE

from django.views.decorators.http import require_POST, require_GET, require_http_methods

@login_required
@require_http_methods(['GET','POST'])
def create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST, request.FILES)
        if form.is_valid():
            article = form.save(commit=False)
            article.user = request.user
            article.save()
            return redirect('articles:detail', article.pk)
    else:
        form = ArticleForm()

    context = {'form': form}
    return render(request, 'articles/create.html', context)

@require_POST -> @require_http_methods(['GET','POST'])
GET, POST 둘다 받도록 수정

DELETE

@require_POST
def delete(request, pk):
    article = Article.objects.get(pk=pk)
    if request.user.is_authenticated:
        if request.user == article.user:
            article.delete()
            return redirect('articles:index')
    return redirect('articles:detail', article.pk)

@login_required 삭제
+if request.user.is_authenticated: 사용

0개의 댓글