Django 8

김기현·2022년 2월 13일
0

django

목록 보기
8/8
post-thumbnail

이번 블로깅은 USER LOG IN & LOG OUT에 대해 다루어보고 이에 필요한 CSRF까지 알아보겠습니다.

Connecting User View, Url for Start

# users/views.py
from django.views import View
from django.shortcuts import render

class LoginView(View):
    def get(self, request):
        return render(request, "users/login.html")

    def post(self, request):
        pass

views에 View를 import하고 Class기반으로 기능들을 작성합니다.

# users/urls.py
from django.urls import path
from . import views

app_name = "users"

urlpatterns = [path("login", views.LoginView.as_view(), name="login")]

app_name이 user가 되도록 app의 url을 연결합니다.

# config/urls.py
urlpatterns = [
    path("users/", include("users.urls", namespace="users")),
	...
]

config 프로젝트 설정파일에도 url을 연결합니다.

Making forms for Login

forms.py에는 Login을 위한 Form이 위치합니다. form에는 Login을 위한 form이 들어있고, email과 password를 입력해주었습니다.

# users/forms.py
from django import forms


class LoginForm(forms.Form):

    email = forms.EmailField()
    password = forms.CharField(widget=forms.PasswordInput)

email이 user의 아이디가 되도록 코드를 구성했습니다. 이것으로 user가 중복되는지 체크하고 email도 체크하는 2번의 작업을 1번으로 줄일 수 있습니다.
widget=forms.PasswordInput으로 user가 비밀번호를 입력할 때 비밀번호가 가려질 수 있도록 widget을 설정 합니다.

View 채우기

# users/views.py
from django.views import View
from django.shortcuts import render
from . import forms


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

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

get 메서드에서는 LoginForm의 형식을 form으로 저장하고, form을 context안으로 보내주기 위해 {"form": form}를 지정해주어 login창이 form을 가지도록 합니다.

Template 채우기

프론트엔드 부분을 Template으로 채웠습니다.

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

login 버튼을 누르면 POST할 수 있도록 form의 메서드를method="POST"으로 지정하고, users:login으로 url을 거치도록 합니다.

사이트 간 요청 위조 확인에 오류가 생기지 않도록 csrf_token을 설정해줍니다.

csrf_token

사이트 간 요청 위조(Cross-site request forgery, CSRF)는 웹사이트 취약점 공격의 하나로, 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격을 말합니다.

예시로 설명을 풀어가겠습니다.

'페이스북' 웹사이트에 로그인을 할 때 웹사이트는 쿠키를 줍니다. 그래서 페이스북에 접속할 때마다 자동적으로 쿠키를 페이스북에 보내는데요. 문제는 사용자가 페이스북이 아닌 다른 웹사이트를 방문했을 때 생깁니다.

다른 웹사이트에, 예를 들어 인스타그램, 버튼이나 이상한 자바스크립트를 가지고 있고 그 버튼을 누르면 인스타그램한테가 아닌 페이스북한테 ajax 등을 사용해 무언가를 요청합니다.

이 때 그 요청은 사용자의 브라우저에서 일어났기 때문에 브라우저는 자동적으로 쿠키를 보내는데, 인스타그램에서 버튼을 누르면 페이스북 쿠키를 페이스북 백엔드쪽으로 보내며 이때 공격자가 의도한 행위(비밀번호를 어떻게 바꿀지 등..)대로 공격합니다.

그래서 악의적인 링크 등을 눌러도 사용자의 웹사이트가 방어(대응)을 하도록 설계되어있는데, 장고는 대응의 방법으로{%csrf_token %}을 사용합니다.

공격자들이 아무리 post request를 보낸 것을 찾았다 할지라도 token의 번호가 다르기 때문에 공격을 대응합니다.

Validating Data

입력된 데이터가 유효한지 확인하기 위해 2가지를 수행합니다. 첫 번째는 is_valid라는 function을 사용합니다.

# users/forms.py
from django import forms
from . import models


class LoginForm(forms.Form):

    email = forms.EmailField()
    password = forms.CharField(widget=forms.PasswordInput)

    def clean(self):
        email = self.cleaned_data.get("email")
        password = self.cleaned_data.get("password")
        try:
            user = models.User.objects.get(email=email)  # email이 사용자의 username
            if user.check_password(password):
                return self.cleaned_data
            else:
                self.add_error("password", forms.ValidationError("Password is wrong"))
        except models.User.DoesNotExist:
            self.add_error("email", forms.ValidationError("User does not exist"))

이메일의 유효성을 확인하기 위해 clean라는 메서드를 만들어줍니다. (항상 clean_"name"으로 시작해야 합니다.) 그리고 예외처리로 username이 email과 같은 오브젝트가 있다면 email을 return하고 그렇지 않다면 ValidationError를 raise합니다.

이때 유저도 존재하고 비밀번호도 맞다면 cleaned_data안에 저장하도록 합니다. 마지막으로 어떤 필드에서 에러가 왔는지를 알려주기 위해 "password" 혹은 "email"의 값을 입력해주었습니다.

user.check_password(password) 중 check_password 메서드를 통해 주어진 string이 맞으면 True를 return하고 그렇지 않으면 False를 return합니다.

clean()을 사용한다면 해당 코드처럼 한 field에 직접 에러를 추가해야 하고, cleaned_data를 return해야 합니다.

# users/views.py
from django.views import View
from django.shortcuts import render
from . import forms


class LoginView(View):
    def get(self, request):
        form = forms.LoginForm(initial={"email": "itn@las.com"})
        return render(request, "users/login.html", {"form": form})

    def post(self, request):
        form = forms.LoginForm(request.POST)
        if form.is_valid():
            print(form.cleaned_data)
        return render(request, "users/login.html", {"form": form})

Authenticate for Login & Logout

유저를 Login, Logout하기 위해선 두 가지 과정이 필요하며 이는 인증을 하고 로그인을 시키는 것입니다.

우선 인증usernamepassword를 필요로 합니다. 그 후에 user를 return합니다. 이 과정을 거치면 장고가 쿠키를 해주는 등 알아서 진행해줍니다.

post 메서드에 email과 password는 get을 통해 가져옵니다. 그리고 authenticate, login, logout를 import합니다.

# users/views.py
from django.views import View
from django.shortcuts import render, redirect, reverse
from django.contrib.auth import authenticate, login, logout
from . import forms


class LoginView(View):
    def get(self, request):
        form = forms.LoginForm(initial={"email": "davidkim@gmail.com"})
        return render(request, "users/login.html", {"form": form})

    def post(self, request):
        form = forms.LoginForm(request.POST)
        if form.is_valid():
            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})


def log_out(request):
    logout(request)
    return redirect(reverse("core:home"))

username=email, password=password인지 authenticate를 한 후에 user가 있다면 login을 해주고 원래 있던 곳(core:home)으로 redirect(돌려 보냄)를 합니다. reverse는 "core:home"으로 가서 실제 URL을 가져옵니다.

template에서 user가 인증되었으면 다음의 코드로 Login 영역을 더이상 보여주지 않도록 합니다.

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

users앱에 login, logout url을 연결은 필수!

# users/urls.py
urlpatterns = [
    path("login", views.LoginView.as_view(), name="login"),
    path("logout", views.log_out, name="logout"),
]

Shortcuts By Using FormView

FormView를 사용하면 더 적은 코드로 구현할 수 있습니다. FormView에선 Post, Get을 사용하지 않고 구현할 수 있습니다. (FormView에선 template_name을 요구합니다.)

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


class LoginView(FormView):

    template_name = "users/login.html"
    form_class = forms.LoginForm	# LoginForm(initialize) 하지 않습니다.
    success_url = reverse_lazy("core:home")	 
	# reverse_lazy : 자동으로 호출하지 않고 View가 필요로 할 때 호출합니다.
     
    def form_valid(self, form):
        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)


def log_out(request):
    logout(request)
    return redirect(reverse("core:home"))

classy class based view에서 더 많은 view들을 검색할 수 있으며, 추가적으로 여기서는 사용되지 않았지만 장고에는 authentication_form, PasswordChangeForm, PasswordResetForm, SetPasswordForm, UserChangeForm, UserCreationForm 등 다양한 Form이 있습니다.

profile
피자, 코드, 커피를 사랑하는 피코커

0개의 댓글