본문 바로가기
백엔드/Django

[Django] allauth 패키지

by 토마토베이컨수프 2021. 10. 25.

django-allauth 패키지란?

장고에서 유저 기능 구현을 위해 쓰이는 패키지 입니다. 장고에서 제공하는 것들 중 django.contrib.authdjango-allauth가 있는데요,

이 둘의 차이는 다음과 같습니다.

  • django.contrib.auth : 장고에 포함되는 앱으로, 내부적으로 urls, views, forms, models가 있습니다.
    • AbstractUser : django.contrib.auth에서 제공하는 유저 모델로, username, password, first_name, last_name, email 등의 필더들을 미리 제공하고, 추가적인 필드도 커스터마이징 할 수 있습니다.
    • AbstractBaseUser : 역시 django.contrib.auth에서 제공하는 유저 모델로, 기본적인 필더를 제공하지 않고 우리가 직접 다 만들어 줘야 합니다.
  • django-allauth : models를 제외한 유저 시스템 구현을 위한 기능들을 제공하는 패키지 입니다.

 

이제 이 둘을 이용하여 유저 기능을 구현해 봅시다.

 


유저 모델 생성

# models.py

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

AbstractUser을 상속받아 이렇게 클래스를 만들어주기만 하면 사실상 유저 모델이 만들어졌다 할 수 있습니다. 모델 안에 이미 기본적인 속성들이 다 있기 때문이죠.

 

그리고 settings.py에 다음과 같이 추가합니다.

# settings.py

AUTH_USER_MODEL = "my_app.User"

후에

python manage.py makemigrations

python manage.py migrate 으로 마이그레이션 해서 유저 모델을 등록합니다.

 

그리고 admin.py에 다음과 같이 추가하면

# admin.py

from .models import User
from django.contrib.auth.admin import UserAdmin

admin.site.register(User, UserAdmin)

어드민 페이지에 들어 갔을 때 유저와 관련된 설정 인터페이스들을 제공받을 수 있습니다.

 


allauth 셋업

django allauth 공식문서입니다.

https://django-allauth.readthedocs.io/en/latest/installation.html

 

Installation — django-allauth 0.43.0 documentation

Post-Installation In your Django root execute the command below to create your database tables: Now start your server, visit your admin pages (e.g. http://localhost:8000/admin/) and follow these steps: Add a Site for your domain, matching settings.SITE_ID

django-allauth.readthedocs.io

 

이 문서의 내용에 따라 allauth 패키지를 설치하고 settings.py를 수정해주고 migrate 해 줍니다. 소셜로그인에 대한 기능은 보류해줘도 상관 없습니다.

그리고 추가적으로 다음과 같이 추가하면

# settings.py

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

회원가입 또는 비밀번호 찾기를 할 때 이메일을 전송해 인증을 받을 수 있는 기능을 사용할 수 있게 됩니다.

 


url 처리

allauth를 이용해 구현된 로그인 페이지와 회원가입 페이지는 기본적으로 "/login/", "/signup/" 의 url이름을 가집니다. 그리고 회원가입이나 로그인의 액션을 취하게되면 "/account/profile/" 이라는 경로로 이동하게 됩니다.

 

저 경로가 아닌 메인 페이지로 바로 redirect되게 하려면 어떻게 해야 할까요?

메인 페이지를 렌더링하는 뷰 함수가 index, 그리고 url name이 index라 가정하고 다음과 같이 url을 설정합니다.

# 프로젝트 디렉토리 urls.py
path("", include("my_app.urls"))

# my_app 디렉토리 urls.py
path("", views.index, name="index")

 

그리고 settings.py로 가서 다음과 같이 추가하면 끝입니다.

# settings.py

ACCOUNT_SIGNUP_REDIRECT_URL = "index"
LOGIN_REDIRECT_URL = "index"

 


유저 접근 방법

views.py에서 현재 유저에게 접근하기 위해서 request.user을 쓰거나 request.user.email , request.user.username 같이 유저 속성에도 접근할 수 있습니다.

그리고 템플릿에선 {{user}} 같은 방식으로 접근할 수 있습니다.

그리고 로그인 상태 여부를 확인하려면 request.user.is_authenticated의 값을 확인하면 됩니다. True면 로그인된 상태, False면 로그아웃된 상태입니다.

 

따라서 만약 템플릿 코드를 다음과 같이 작성하게 되면

<navbar>
    {% if user.is_authenticated %}
        <a href="{% url account_logout %}">로그아웃</a>
    {% else %}
        <a href="{% url account_login %}">로그인</a>
        <a href="{% url account_signup %}">회원가입</a>
    {% endif %}
</navbar>

로그인이 되어있을 땐 로그아웃 링크를 표시하고, 로그인이 되어있지 않을 때는 로그인과 회원가입 링크를 표시하는 페이지를 만들 수 있습니다.

 

그런데 자세히 보면 로그아웃과 로그인, 회원가입에 대한 url name이 이미 정해져 있는 것을 볼 수 있습니다. 이는allauth 내부에서 미리 정의되어 있는 이름으로, allauth의 깃허브 소스코드를 통해 그 내용을 확인할 수 있습니다.

https://github.com/pennersr/django-allauth/blob/master/allauth/account/urls.py

 

GitHub - pennersr/django-allauth: Integrated set of Django applications addressing authentication, registration, account managem

Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication. - GitHub - pennersr/django-allauth: Integrate...

github.com

 

그리고, 기본적으로 로그아웃 링크를 누르면 로그아웃을 할 수 있는 페이지로 따로 가게 되는데, settings.py에 다음과 같은 코드를 추가해 로그아웃 링크를 누르자마자 바로 로그아웃을 하게 해줄 수 있습니다.

# settings.py

ACCOUNT_LOGOUT_ON_GET = True

 


이메일로 로그인

# settings.py

# 디폴트값으로는 username을 통해 로그인하게 되어 있습니다
ACCOUNT_AUTHENTICATION_METHOD = "email"

# 만약 둘 다 로그인에 사용하고 싶으면
ACCOUNT_AUTHENTICATION_METHOD = "username_email"

# 회원가입 시 아이디 입력을 받지 않고 이메일만 입력을 받고 싶다면
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False

 

이렇게 되면 기존에 작성하였던 models.py의 유저모델도 출력시 기본값으로 이메일이 리턴되도록 바꿔줘야겠죠?

class User(AbstractUser):
    def __str__(self):
         return self.email

 


로그인의 원리

로그인의 원리에 대해 간단히 알아봅시다. 

 

웹사이트에서 사용자가 로그인을 해 서버로 request를 보내면, 서버는 session이라는 것을 만들고 session-id라는 것을 브라우저에게 전달해줍니다. 그러면 브라우저는 그걸 받아서 웹사이트의 Cookie라는 곳에 저장합니다. 이렇게 되면 브라우저가 서버로 request를 보낼 때 cookie도 같이 보내게 되는데, 그때 서버는 쿠키의 session-id를 확인해 자기가 가지고 있는 session-id와 매칭시켜 session에 있는 유저를 현재 유저, 즉 로그인되어 있는 유저로 설정해줍니다.

이 때문에 로그인이 계속 유지가 되는 것이죠. 만약 이 쿠키세션이 삭제되면 로그아웃 상태가 되는겁니다. 개발자도구의 ApplicationCookies로 들어가면 이 session을 확인할 수 있습니다.

 

우리의 로그인화면에도 'Remember me'라는 체크박스가 있는데, 이것은 브라우저를 닫아도 세션 쿠키를 유지할 것인지 삭제할 것인지를 정해주는 기능을 합니다.

만약 항상 유저를 기억하게 하고 싶다면 settings.py로 가서 

# settings.py

ACCOUNT_SESSION_REMEMBER = True
# 쿠키의 유지 시간
SESSION_COOKIE_AGE = 3600

ACCOUNT_SESSION_REMEMBER을 True로 설정하면 됩니다. 그리고 추가적으로 쿠키의 유지 시간도 설정이 가능합니다.

 

쿠키 유지 시간의 기본값은 2주고, 만약 사용자가 직접링크를 통해 로그아웃을 해주는 경우 서버에 있는 세션 데이터는 삭제됩니다.

근데 만료시간이 다 되서 로그아웃되는 경우는 서버에 세션들이 쌓이게 되는데, 이게 계속되면 서버 성능이 떨어지게 됩니다. 따라서 python manage.py clearsessions 같이 세션을 지워주는 이 명령어를 정기적으로 수동 또는 자동으로 실행해줘야합니다.

 


새로운 필드 추가

닉네임을 나타내는 새로운 필드를 추가해 줍시다.

# models.py

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

이미 db에 데이터들이 있다면 새로운 필드를 추가할 때 에러가 발생해 마이그레이션이 되지 않습니다. 따라서 null속성값을 True로 설정합니다. 그러면 기존 유저들의 nickname은 일단 null값이 될 것입니다.

 

그리고, 어드민 페이지에 유저모델의 추가필드에 대한 정보를 나타내주기 위해 다음과 같이 admin.py에 작성해줍니다.

UserAdmin.fieldsets += ("Custom fields", {"fields": ("nickname",)})

 

이제 회원가입 폼에도 닉네임을 입력 받아 계정을 생성할 수 있게 해줍시다.

# forms.py

from django import forms
from .models import User

class SignupForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ["nickname"]

    def signup(self, request, user):
        user.nickname = self.cleaned_data["nickname"]
        user.save()

모델폼을 쓸 것이니 Meta클래스를 만들고 그 안에 model 변수에는 유저 모델을, fields에는 새로 추가해준 필드에 대한 이름만을 넣어줍니다. 그리고 signup함수에서 우선 폼에 기입된 데이터들을 cleaned_data로 불러오고 저장하는 로직을 작성해줍니다.

 

마지막으로, 해당 signup폼을 사용하겠다는 설정을 settings.py에 추가해줍시다.

# settings.py

ACCOUNT_SIGNUP_FORM_CLASS = "my_app.forms.SignupForm"

 


회원가입 정보 유효성 검사

유효성 검사에 대한 에러 메세지를 우리가 정한 메세지로 커스터마이징 할 수 있습니다. 아래는 unique에러에 대한 에러 메세지를 설정하는 방법입니다.

# models.py

class User(AbstractUser):
    nickname = models.CharField(
        max_length=15, unique=True, null=True,
        error_messages={"unique": "이미 존재하는 닉네임 입니다."}
    )

 

 

이 말고도 개인적으로 만든 유효성 검사 함수를 만든 후 모델 필드의 validators속성에 리스트 형태로 넣어주는 방식으로 유효성 검사를 할 수 있습니다.

자세한 방법은 이 페이지를 참조해 주세요!

https://tomatobaconsoup.tistory.com/8

 

[Django] 폼(Form)에 관하여

From 이란? 폼은 사용자가 입력한 데이터를 서버로 전송하기 위한 방식입니다. 이렇게 사용자의 데이터를 입력 받을 수 있는 양식이라는 것이 존재하고, 데이터를 입력하게 되면 폼의 내부 전송

tomatobaconsoup.tistory.com

 

다만, 비밀번호에 대한 validator는 함수가 아니라 클래스형태로 만들어야 합니다.

# validators.py

from django.core.exceptions import ValidationError

class CustomPasswordValidator:
    def validate(self, password, user=None):
        if len(password) < 6:
            raise ValidationError("비밀번호가 너무 짧습니다.")
    def get_help_text(self):
        return "어드민 페이지에서 비밀번호 설정"

validate란 메소드에는 우리가 원하는 validation을 구현하면 되고, get_help_text메소드는 어드민페이지에서 비밀번호를 바꿀때 필요한 내용을 리턴하는 기능을 합니다.

 

그리고 settings.py에 다음과 같이 AUTH_PASSWORD_VALIDATORS 안에 비밀번호 유효성 클래스를 추가합니다.

# settings.py

AUTH_PASSWORD_VALIDATORS = [
    {"NAME": "my_app.validators.CustomPasswordValidator"},
        ...
]

ACCOUNT_PASSWORD_INPUT_RENDER_VALUE = True

ACCOUNT_PASSWORD_INPUT_RENDER_VALUE을 True로 설정해주면 회원가입 폼이 유효하지 않아도 폼에 작성했던 내용을 모두 지우지 않고 입력했던 비밀번호를 그대로 유지시켜줍니다.

 


이메일 인증

우선 settings.py에 관련된 설정들을 몇 가지 추가해줍시다.

# settings.py

ACCOUNT_EMAIL_VARIFICATION = "mandatory"    # 인증을 안하면 로그인 자체를 못합니다
ACCOUNT_EMAIL_VARIFICATION = "optional"	    # 메일은 발송되지만 이메일을 인증안해도 로그인은 됩니다 -> 기본값
ACCOUNT_EMAIL_VARIFICATION = "none"	    # 아예 이메일 인증 자체를 안합니다

# 이메일로 전송된 링크를 누르면 바로 회원가입이 완료됩니다
ACCOUNT_CONFIRM_EMAIL_ON_GET = True

 

그리고 프로젝트 urls.py에 이메일인증완료를 확인하는 페이지의 경로를 추가해줍시다. 단순히 템플릿만 렌더링 할 것이기 때문에 TemplateView라는 제네릭 뷰를 사용하도록 하겠습니다.

# urls.py

from django.views.generic import TemplateView
# urlpatterns
path("email-comfirmation-done/", 
    TemplateView.as_view(template_name="my_app/email_comfirmation.html"),	
    name="account_email_confirmation"),

 

그리고 이렇게 로그인 상태면 홈으로 가고 아니면 로그인페이지로 가게 하는 이메일 인증 완료 페이지를 작성해 줍니다.

<!--email_comfirmation.html-->

<p>이메일 인증이 완료되었습니다</p>
{% if user.is_authenticated %}
    <a href="{% url 'index' %}"> 홈으로 이동</a>
{% else %}
    <a href="{% url 'account_login' %}">로그인 페이지로 이동</a>
{% endif %}

 

마지막으로 settings.py에 속성 몇 가지를 더 추가해줍니다.

# settings.py

ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = "account_email_confirmation"
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = "account_email_confirmation"

이처럼 이메일인증완료 페이지의 name을 각 속성에 설정해주면 이메일 인증 확인을 완료했을 때 redirect할 페이지가 해당 name을 가진 페이지로 정해집니다.

 


비밀번호 관리

로그인 후 메인 홈페이지에서 비밀번호를 변경 할 수 있는 기능을 추가해봅시다.

우선 메인 페이지 템플릿에 다음과 같이 추가합니다.

<a href="{% url 'account_change_password' %}">비밀번호 변경</a>

위에서 사용한 비밀번호 변경 페이지로 가는 url name은 위에서 했듯이 allauth 깃허브 소스코드 에서 확인 가능합니다.

 

하지만 이대로만 두면 비밀번호를 변경해도 현재 있는 똑같은 비밀번호 변경 페이지로 redirect하게 됩니다. allauth에서 처음부터 그렇게 설정이 되어 있는 겁니다. 따라서 우리는 이 기능을 상속받아 메인 홈페이지로 가도록 변경해줍시다.

# views.py

from allauth.account.views import PasswordChangeView
from django.urls import reverse

class CustomPasswordChangeView(PasswordChangeView):
    def get_success_url(self):
        return reverse("index")

이렇게 PasswordChangeView를 상속받아서 get_success_url 메소드를 오버라이딩 해 메인 페이지에 해당하는 url name을 넘겨주면 됩니다.

 

이제 메인 프로젝트 디렉토리의 urls.py에 다음과 같이 추가합시다.

# urls.py

from myapp.views import CustomPasswordChangeView
# urlspatterns
path("password/change/", 
    CustomPasswordChangeView.as_view(),
    name="account_password_change")

password/change는 allauth에서 설정한 자체적인 url 형식이기 때문에 따라줘야 합니다.

 

그리고, 추가적으로 비밀번호 재설정 링크의 유효기간을 따로 설정해 줄 수도 있습니다.

# settings.py

# django 3.0 이전 버전 (단위: 일)
PASSWORD_RESET_TIMEOUT_DAYS = 30
# django 3.1 이후 버전 (단위: 초)
PASSWORD_RESET_TIMOUT = 100000

 


참고 자료

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

[Django] 접근 제어  (0) 2021.11.04
[Django] 제네릭 뷰  (0) 2021.10.11
[Django] 페이지네이션(Pagination) 기능  (0) 2021.10.10
[Django] 폼(Form)에 관하여  (0) 2021.10.05
[Django] MVT 구조  (0) 2021.10.02