본문으로 바로가기

local login과 CSRF 해결

category Django, Flask/🔫 Django 2020. 10. 5. 08:00

간단히 email과 password를 받아 로컬 로그인을 구현해보도록하자.

login, authentication 로직은 우선 생략하고 간단히 로그인과 비밀번호를 받는 form을 구현해보자.

from django import forms
from . import models


class LoginForm(forms.Form):

    # passwordField는 없습니다
    email = forms.EmailField()
    password = forms.CharField(widget=forms.PasswordInput)

    # 필드 간의 관계 등 다른 validation을 위해서는 clean함수를 사용하자.
    # clean_[필드명] 꼴이 verbose하다고 생각하면 clena으로 한 방에 통합해도 된다.
    def clean(self):
        email = self.cleaned_data.get("email")
        password = self.cleaned_data.get("password")

        try:
            user = models.User.objects.get(email=email)
            if user.check_password(password):
                return self.cleaned_data
            else:
                # clean 메서드를 사용할 시 어느 필드에서 오류가 났는지 add_error에서 명시해줄 것
                self.add_error("password", forms.ValidationError("password is wrong"))
        except models.User.DoesNotExist:
            self.add_error("email", forms.ValidationError("User does not exist"))
from django.shortcuts import render
from django.views import View
from . import forms

# Create your views here.
class LoginView(View):
    def get(self, request):
        form = forms.LoginForm()
        return render(request, "users/login.html", {"form": form})

    def post(self, request):
        pass
<!-- base -->
{% extends "base.html" %}

<!-- pagename block -->
{% block page_name%} Login {% endblock page_name%}

<!-- block-->
{% block content %}
<div>Login</div>
<form method="POST" action="{% url "users:login" %}">
  {% csrf_token %}
  {{form.as_ul}}
  <button>Login</button>
</form>
{% endblock content %}

 

{% csrf_token %} ?

 

{% csrf_token %}을 제외하고 form을 작성하고 submit을 날려보면 403 forbidden이 뜬다.

 

 

CSRF란 Cross Site Request Forgery의 약자이다.

해당 공격과 방어 메카니즘은 공식 문서를 참고하자

docs.djangoproject.com/en/2.2/ref/csrf/

 

공식문서와 별도로 CSRF를 프론트 javasciprt 단에서 해결하는 방법도 아래 포스트에서 소개하고 있으니 참고해보자.

doctorson0309.tistory.com/605

 

 

결국 해당 웹사이트에서 보낸 요청인지를 식별하는 되는 문제이다. django에서는 csrf_token 태그를 form 태그 내부에 넣어서 간단히 해결할 수 있다.

 

In any template that uses a POST form, use the csrf_token tag inside the <form> element if the form is for an internal URL, e.g.:

<form method="post">{% csrf_token %}

This should not be done for POST forms that target external URLs, since that would cause the CSRF token to be leaked, leading to a vulnerability.

 

즉, 아래와 같이  {% csrf_token %} 만 추가해주면 되겠다.

<form method="POST" action="{% url "users:login" %}">
  {% csrf_token %}
  {{form.as_ul}}
  <button>Login</button>
</form>

추가한 다음 관리자 창을 켜보면 다음과 같이 토큰을 확인할 수 있다.

 

 

authenticate/login

[공식문서](docs.djangoproject.com/en/3.1/topics/auth/default/#how-to-log-a-user-in)

 

 

django.contrib.auth에서 제공하는 authenticate 메서드와 login, logout 메서드를 통해 쉽게 로그인시킬 수 있다.

우선 clean() 혹은 clean_[메서드]()를 통해 form Validation까지 끝냈다면 통과한 cleaned_data을 이용하여 로그인을 시켜봅시다.

from django.shortcuts import render, redirect, reverse
from django.views import View
from django.contrib.auth import authenticate, login
from . import forms

# Create your views here.
class LoginView(View):
    def get(self, request):
        form = forms.LoginForm()
        return render(request, "users/login.html", {"form": form})

    def post(self, request):
        form = forms.LoginForm(request.POST)

        # is_valid() : clean()을 전부 통과하면 True 반환
        if form.is_valid():
            # clean()에서 return한 data를 확인할 수 있습니다.
            email = form.cleaned_data.get("email")
            password = form.cleaned_data.get("password")
            user = authenticate(request, username=email, password=password)
            if user is not None:
                login(request, user)
                return redirect(reverse("core:home"))

        return render(request, "users/login.html", {"form": form})

 

 

authentication check

 

해당 유저가 로그인 중인지, 로그인 중이 아닌지를 체크하기 위해 is_authenticated 속성을 체크합니다.

다음과 같이 사용할 수 있습니다.

  <ul>
    {% if user.is_authenticated %}
      <li><a href="{% url "users:login" %}">Log out</a></li>
    {% else %}
      <li><a href="{% url "users:login" %}">Log in</a></li>
    {% endif %}
  </ul>

 

 

logout

django.contrib.auth 에서 logout 함수를 이용하면 됩니다.

from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.

 


다른 방법들

 

(1) LoginView를 이용하기

username을 아이디로 사용하도록 강제하기 때문에, 또 추상화의 정도가 높기 때문에 여기서는 사용하지 않기로 했다.

 

docs.djangoproject.com/en/3.1/topics/auth/default/#django.contrib.auth.views.LoginView

 

 

(2) FormView를 이용해보기

 

위와 같은 방법으로 login/logout을 구현할 수 있지만 조금 더 편리하고 코드를 덜 작성하고 싶다면 FormView를 이용해보는 것도 방법이다.

 

 

절충안으로 FormView를 사용하곤 한다.

https://docs.djangoproject.com/en/3.1/topics/auth/default/#all-authentication-views

ccbv.co.uk/projects/Django/3.0/django.views.generic.edit/FormView/

 

from django.shortcuts import render, redirect, reverse
from django.urls import reverse_lazy
from django.views.generic import FormView
from django.contrib.auth import authenticate, login, logout
from . import forms

class LoginView(FormView):

    template_name = "users/login.html"
    form_class = forms.LoginForm
    success_url = reverse_lazy("core:home")  # reverse를 쓰면 Error

    def form_valid(self, form):
        """If the form is valid, redirect to the supplied URL."""
        email = form.cleaned_data.get("email")
        password = form.cleaned_data.get("password")
        user = authenticate(self.request, username=email, password=password)
        if user is not None:
            login(self.request, user)
        return super().form_valid(form)

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체