장고 기본 인증 구현

guava·2021년 12월 20일
0

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.

장고를 활용하면 인증에 대해 프레임워크 레벨에서 지원해주는 것들이 있다.

settings.py에서 다음 설정이 가능하다.
1. 유저 모델 지정
2. 로그인 URL, 로그인 후 리다이렉트 URL, 로그아웃 후 라디아렉트 URL 등에 대한 설정
2. 비밀번호 재설정 페이지의 유효기간 설정

global_settings

global_settings.py에서 인증 설정 부분을 발췌했다. settings.pyglobal_settings.py를 상속받아서 동작하기 때문에 global_settings.py를 참고하면 어떤 설정이 가능한지 확인할 수 있다. 우리는 setting.py에서 LOGIN_URL 등을 오버라이드 해주면 된다.

LOGIN_URL은 로그인 페이지의 URL이다. 만약 login_required데코레이터를 활용했다면, 인증되지 않은 클라이언트가 접속했을 때 이 URL로 리디렉션 한다.

LOGIN_REDIRECT_URL은 로그인 이후에 리디렉션 할 URL이다. URL에서 지정된 next인자가 없다면 이 설정의 URL로 리디렉션 한다.

# django.conf.global_settings.py
# ...

##################
# AUTHENTICATION #
##################

AUTH_USER_MODEL = 'auth.User'

AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']

LOGIN_URL = '/accounts/login/'

LOGIN_REDIRECT_URL = '/accounts/profile/'

LOGOUT_REDIRECT_URL = None

# The number of days a password reset link is valid for
PASSWORD_RESET_TIMEOUT_DAYS = 3

# The number of seconds a password reset link is valid for (default: 3 days).
PASSWORD_RESET_TIMEOUT = 60 * 60 * 24 * 3

# the first hasher in this list is the preferred algorithm.  any
# password using different algorithms will be converted automatically
# upon login
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

AUTH_PASSWORD_VALIDATORS = []

# ...

장고의 contrib.auth앱에서 urls.py를 뜯어보면 장고에서 지원해주는 인증 기능들을 대략적으로 파악할 수 있다. admin에서 이를 활용하는것으로 보이며 이를 참고한다면 내가 만드는 프로젝트에서도 비슷하게 구현이 가능할 것이다.

아래 코드를 보면 urlpattenrs에서 views.LoginView, views.LogoutView, views.PasswordChangeView 등을 활용해서 로그인뷰, 로그아웃뷰, 비밀번호 변경 뷰가 구현되어 있다.
우리의 프로젝트에서 이 뷰들을 활용해서 바로 구현이 가능하고 상속받아서 커스텀도 가능하다.

django.contrib.auth.urls.py

# The views used below are normally mapped in the AdminSite instance.
# This URLs file is used to provide a reliable view deployment for test purposes.
# It is also provided as a convenience to those who want to deploy these URLs
# elsewhere.

from django.contrib.auth import views
from django.urls import path

urlpatterns = [
    path('login/', views.LoginView.as_view(), name='login'),
    path('logout/', views.LogoutView.as_view(), name='logout'),

    path('password_change/', views.PasswordChangeView.as_view(), name='password_change'),
    path('password_change/done/', views.PasswordChangeDoneView.as_view(), name='password_change_done'),

    path('password_reset/', views.PasswordResetView.as_view(), name='password_reset'),
    path('password_reset/done/', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
    path('reset/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    path('reset/done/', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]

프로젝트에서 인증 구현하기

Accounts앱을 통한 인증 기능 분리

global_settings.py에서 LOGIN_URL, LOGIN_REDIRECT_URL 설정을 보면 URL이 accounts로 시작한다. 이와 잘 매칭이 되도록 앱 이름을 accounts로 하였으며 다른 이름으로 하여도 무관하다.

accounts앱 생성

$ django-admin startapp accounts

model.py 구성하기

user모델은 장고 모델을 그대로 사용하였다. 여기서는 프로필을 추가로 구현한다.
profile모델은 user모델을 외래키로 가지고 있다.

from django.db import models
from django.conf import settings
from django.test import RequestFactory


class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    address = models.CharField(max_length=100)
    zipcode = models.CharField(max_length=6, validators=[])

urls.py 구성하기

contrib.auth앱의 urls.py의 코드를 복사해도 좋고 원하는 url로 구성해도 좋다. 우선 복사해서 구성해보았다.

로그인 뷰, 로그아웃 뷰, 프로필 뷰, 프로필 수정 뷰, 회원가입 뷰를 지정하였다.

from django.urls import path
from . import views

urlpatterns = [
    path('login/', views.login, name='login'),
    path('logout/', views.logout, name='logout'),
    path('profile/', views.profile_view, name='profile'),
    path('profile/edit/', views.profile_edit, name='profile_edit'),
    path('signup/', views.signup, name='signup')
]

views.py 구성하기

urls.py에서 활용하는 뷰를 구현한다.

로그인 뷰

로그인 뷰는 로그인 폼을 제출하기위한 뷰이다. 장고에서 제공하는 로그인 뷰인 django.contrib.auth.views.LoginView를 그대로 활용하였다.

template_name을 통해 template을 넘긴다.
form_class를 통해 정의한 form을 넘긴다.

# accounts/views.py
from .forms import LoginForm
from django.contrib.auth.views import LoginView

login = LoginView.as_view(template_name='accounts/login_form.html',
                                     form_class=LoginForm)

로그인 뷰에 대한 폼 클래스 참고

from django import forms
from django.contrib.auth.forms import AuthenticationForm

# 기존의 로그인 폼에 우리가 원하는 로직을 추가하고싶다면 다음과 같이 구현한다.
# 기본 로그인 뷰는 AuthenticationForm를 폼 클래스로 사용하고 있다.
# 따라서 AuthenticationForm에 커스텀을 해주고, 우리의 로그인 뷰에 이 커스텀 폼 클래스를 연결해주면 된다.
# AuthenticationForm에 대한 별다른 분석은 필요없다 그냥 이를 상속받아서 구현하면 된다.

class LoginForm(AuthenticationForm):
    answer = forms.IntegerField(help_text='3 + 3 = ?')

    def clean_answer(self): # 회원가입 벨리데이션 추가
        answer = self.cleaned_data.get('answer')
        if answer != 6:  
            raise forms.ValidationError('땡~')
        return answer

로그인 뷰에 대한 템플릿 참고
accounts/templates/accounts/login_form.html

{% extends 'accounts/layout.html' %}
{% load bootstrap4 %}
{% block content %}
<form action="" method="post">
  {% csrf_token %}
  {% bootstrap_form form %}
  {% buttons %}
    <button class="btn btn-primary">로그인</button>
  {% endbuttons %}
</form>
{% endblock %}

로그아웃 뷰

로그아웃 뷰는 사용자 로그아웃을 수행하기 위한 뷰이다. 장고에서 제공하는 로그아웃 뷰인 django.contrib.auth.views.LogoutView를 그대로 활용하였다.

# accounts/views.py
from django.contrib.auth.views import LogoutView

logout = LogoutView.as_view()

로그아웃 후 리디렉션 할 페이지를 지정하는 방법은 여러가지가 있다.
1. LogoutView에 next_page인자로 URL을 넘긴다. 내부적으로 URL Reverse도 수행한다.
LogoutView.as_view(next_url='login')
2. settings.py에 LOGOUT_REDIRECT_URL를 정의한다. (추천)
LOGOUT_REDIRECT_URL = reverse_lazy('login')
3. template에서 next인자를 지정한다.
<a class="nav-link" href="{% url 'logout' %}?next={{ request.get_full_path }}">로그아웃</a>

프로필 뷰 / 프로필 수정 뷰

프로필 뷰는 유저의 프로필을 보여주기 위한 뷰이다. 템플릿 뷰를 상속받아서 그대로 활용하였다.

프로필 수정 뷰는 프로필 수정을 위한 뷰이다.
GET 요청 시 프로필 유무에 따라 인스턴스 없이 폼을 생성하거나, 프로필 정보를 가져와서 폼을 생성해서 보낸다.
POST 요청 시 폼에 요청정보를 담아서 모델에 저장하고 프로필 페이지로 리디렉션한다.

# accounts/views.py

from accounts.forms import ProfileForm
from accounts.models import Profile


class ProfileView(LoginRequiredMixin, TemplateView):
    # 프로필 뷰
    template_name = 'accounts/profile.html'


profile_view = ProfileView.as_view()


@login_required
def profile_edit(request):
    # 프로필 수정을 위한 뷰
    try:
        profile = request.user.profile
    except Profile.DoesNotExist:
        profile = None
    if request.method == 'POST':
        form = ProfileForm(request.POST, request.FILES, instance=profile)
        if form.is_valid():
            profile = form.save(commit=False)
            profile.user = request.user
            profile.save()
            return redirect('profile')
    else:
        form = ProfileForm(instance=profile)
    return render(request, 'accounts/profile_form.html', {
        'form': form
    })

프로필 뷰 관련 템플릿 참고
accounts/templates/accounts/profile.html

{% extends 'accounts/layout.html' %}
{% block content %}
  <h2>User: {{ user }}</h2>
  {{ user.is_authenticated }}
  {% if user.profile %}
    <ul>
      <li>{{ user.profile.address }}</li>
      <li>{{ user.profile.zipcode }}</li>
    </ul>
  {% endif %}
  <a href="{% url 'profile_edit' %}" class="btn btn-primary">프로필 수정</a>
{% endblock %}

accounts/templates/accounts/profile_form.html

{% extends 'accounts/layout.html' %}
{% load bootstrap4 %}
{% block content %}
<form action="" method="post">
  {% csrf_token %}
  {% bootstrap_form form %}
  {% buttons %}
    <button class="btn btn-primary">프로필 수정</button>
  {% endbuttons %}
</form>
{% endblock %}

회원가입 뷰

회원가입을 위한 뷰이다. 장고에서 제공하는 유저 생성 폼인 django.contrib.auth.UserCreationForm을 활용하면 쉽게 구현할 수 있다.

클래스형 뷰에서는 form_valid에서 폼의 유효성 검사 후의 동작을 정의한다.
이를 통해 회원가입 후 바로 로그인이 수행되도록 뷰를 꾸밀 수 있다.

장고 코드에서 보면 로그인 처리를 django.contrib.auth.login을 활용하는것을 확인하였으며 우리의 뷰에서도 회원가입 폼 통과 후에 로그인을 처리하도록 하였다. 또한 success_url을 지정해서 회원가입 후에 프로필 페이지로 이동하도록 하였다.

# accounts/views.py

from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import login as auth_login

class SignupView(CreateView):
    model = User
    form_class = UserCreationForm
    success_url = settings.LOGIN_REDIRECT_URL
    template_name = 'accounts/signup_form.html'

    def form_valid(self, form):
        response = super().form_valid(form)
        user = self.object
        auth_login(self.request, user)
        return response


signup = SignupView.as_view()

0개의 댓글