[Django] - Authentication System (로그인, 회원가입 구현)

KimJiHong·2023년 10월 8일
1

Django and API

목록 보기
3/9
post-thumbnail

게시글 전체 소스코드는 GitHub 에서 다운로드 가능합니다.

Authentication & Authorization Module

장고에서는 기본적으로 사용자 인증(authentication system) 을 위한 로그인과 로그아웃, 회원가입 을 위한 기능들을 제공해준다.

인증(authentication)을 위한 모듈

  • authenticate : 클라이언트로 부터 입력받은 ID 와 PASSWORD 를 User 데이터베이스 내에서 해당 객체가 있는지 검사하고, 있다면 객체를 반환.

인가(authorization)를 위한 모듈

  • login : authenticate 함수를 통해서 DB 내에서 반환받은 객체를 현재 session 데이터 내에 로그인 데이터로 저장.
  • logout : session cookie 에서 클라이언트의 로그인 정보를 삭제.

Logic

# tree

mysite/
	settings.py/
	urls.py/
users/
	templates/
    	login.html/
        join.html/
    urls.py/
    views.py/
    models.py/

Login page

로그인 페이지를 만들기에 앞서, 제일 먼저 url 설정을 해준다.

# users/urls.py

from django.urls import path
from . import views

app_name = 'users'

urlpatterns = [
    path('login/', views.login_index, name='LoginUrl'),
]

클라이언트가 최초로 로그인 페이지를 request 시, 로그인 html 을 response

클라이언트가 사용자 인증(authentication) reqeust 시,
클라이언트가 입력한 ID 와 PASSWORD 를 authenticate 검사를 통해 일치하는 객체를 검사한다.

이때, 일치하는 객체가 있다면 사용자 인가를 위해 session cookie 내에 사용자 로그인 정보를 저장해줘야한다.

그럼, 위 Logic 을 소스코드로 구현해보자.

templates 에 클라이언트가 로그인 페이지를 요청시 보여줄 login.html 을 아래와 같이 작성해주었다.

!-- users/templates/login.html -->

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>김지홍</title>
</head>
<body>

    <h1>로그인 페이지</h1>
    <br>
    <div>로그인</div>
    <br>

    {% if user.is_authenticated %}

        <div>{{ user.username }} 님으로 로그인 성공</div>
        <form action="{% url 'users:LogoutUrl' %}">
            {% csrf_token %}

            <button type="submit">로그아웃</button>
        </form>

    {% else %}

    <form action="{% url 'users:LoginUrl' %}" method="post">
        {% csrf_token %}

        <div>아이디 : <input name="username" type="text"></div>
        <div>비밀번호 : <input name="password" type="password"></div>
        <div><button type="submit" name="account" value="login">로그인</button></div>
        <div><button type="submit" name="account" value="join">회원가입</button></div>

    </form>
    {% endif %}

</body>
</html>

장고 템플릿 언어 user.is_authenticated 를 통해 클라인언트가 인증받은 사용자인지 확인한다.

만약 해당 클라이언트가 인증된 사용자일 때와 인증되지 않았을 때, 서로 다른 html 소스를 보여주기 위해서 if 문을 사용하였다.

이제, views 에서 클라이언트의 request 에 따라서 어떻게 동작할 것인지 설정해주자.
(주석도 열심히 달았으니 확인해보길 바란다.)

# users/views.py

from django.shortcuts import render, redirect

# django 내장 모듈 authenticate, login, logout
# authenticate 함수는 클라이언트로 부터 입력받은 ID와 PASSWORD를 통해서 
# 사용자 정보가 저장된 DB 내에서 해당하는 객체가 있는지 검사하고, 
# 올바른 경우 해당 사용자 객체를 반환하고, 올바르지 않은경우에는 NONE을 반환한다.

# login 함수는 user 를 로그인 상태로 만들어준다. 즉, authenticate 함수를 통해서 
# DB 내에서 반환받은 객체를 받아와서 현제 session 데이터 내에 로그인 데이터를 저장한다. 
# 또, 'request.user' 를 사용해서 현재 로그인된 사용자 정보를 가져올 수 있게 해준다.

# logout 함수는 session cookie 에서 클라이언트의 로그인 정보를 삭제해준다.
from django.contrib.auth import authenticate, login, logout
from .models import UserDB


def login_index(request):
    if request.method == 'POST':
        account = request.POST.get('account')

        if account == 'login':
            username = request.POST.get('username')
            password = request.POST.get('password')

            # authenticate 검사 (사용자 인증) => return 'None' or USERS object in 'userDB'
            userObject = authenticate(username = username, password = password)

            # is Not None (login success)
            if userObject is not None:

                # request & user object 를 django 내장 모듈
                # login 의 인자로 사용하여 로그인 가능하게 해줄수 있다.
                # template 에서 {{ user.is_authenticated }} 으로
                # 로그인된 사용자 데이터를 알 수 있다.
                login(request, userObject)

            
            # is None (login fail)
            else:
                print('fail')

        # account = 'join' (회원가입)
        else:
            return redirect('users:JoinUrl')

    else:
        pass

    return render(request, 'login.html', { })

아직 회원가입 페이지와 로그아웃 기능은 작성하지 않았지만, 미리 랜더링 해놨다.

이제 로그인 페이지가 정상적으로 작동하는지 확인해보기 위해 createsuperuser 를 통해 'admin' 이라는 계정을 생성한 뒤, runserver 했다.

$ python3 manage.py createsuperuser
# super 계정 생성

$ python3 manage.py runserver

처음 GET 요청을 통해서 login url 로 들어온다면 위 사진과 같이 로그인을 위한 form 을 보여준다.

사용자 authentication 이 완료되면, 로그인 form 이 사라지고 로그아웃 버튼이 보인다.

Logout

authenticate 와 login 모듈을 사용하여 사용자 인증이 완료 되었다면 session cookie 내에 사용자 로그인 정보가 저장된다.

logout 기능은 session cookie 내에 저장된 사용자 로그인 정보를 삭제하면 된다.

우리는 장고의 내장 모듈 logout 을 사용하면 서버에서 session 쿠키에 대한 정보를 지워 사용자 authorization 이 사라지게 된고, 사용자는 다시 인가를 위해서는 로그인(인증)을 진행해야 한다.

logout 로직은 다른 페이지를 클라이언트에 보여줄 필요없이 단순히 해당 session 데이터만 지우면 되므로 매우 간단하게 구현 가능하다.

# users/views.py

# ...
def logout_index(request):
    logout(request)
    return redirect('users:LoginUrl')
# users/urls.py

# ...
urlpatterns = [
	# ...
    path('logout/', views.logout_index, name='LogoutUrl'),
]

그냥 request 에 있는 session 데이터를 서버에서 지우고 다시 로그인 페이지로 redirect 하면 구현이 끝난 셈이다.

Join page

새로운 사용자를 등록하기 위해서 회원가입 페이지에서는 몇가지 고려사항이 있다.

  • ID 또는 PASSWORD 를 작성하지 않고 form 을 제출시
  • PASSWORD 설정시 비밀번호 확인을 통해서 두 PASSWORD 일치 비교
  • 입력한 ID 가 이미 다른 사용자가 사용중인가?

나는 위 3가지 경우를 서버에서 예외 처리로 구현해서 문제가 발생한다면, 다시 join 페이지로 이동하게 구현해보자.

templates 에 회원가입시 입력할 form 을 아래와 같이 구성하여 html 파일을 넣었다.

!-- users/templates/join.html -->

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>김지홍</title>
</head>
<body>

    <h1>아이디 만들기</h1>
    <div>새로운 아이디</div>

    {% if error_msg %}
    <div>
        {{ error_msg }}
    </div>
    {% endif%}

    <form action="{% url 'users:JoinUrl' %}" method="post">
        {% csrf_token %}

        <div>아이디 : <input type="text" name="username"></div>
        <div>비밀번호 : <input type="password" name="password"></div>
        <div>비밀번호 확인 : <input type="password" name="password_check"></div>
        <div>
            <button type="submit" value="create" name="account">완료</button>
            <button type="submit" value="cancel" name="account">취소</button>
        </div>

    </form>

</body>
</html>

이제, 위에서 언급한 3가지 고려사항을 반영하여 logic 을 완성해보자.
# users/urls.py

# ...
urlpatterns = [
	# ...
    path('join/', views.join_index, name='JoinUrl'),
]
# users/views.py

# ...
def join_index(request):
    if request.method == 'POST':
        account = request.POST.get('account')
        print(account)

        if account == 'create':
            new_username = request.POST.get('username')
            new_password = request.POST.get('password')
            new_password_check = request.POST.get('password_check')
            print(new_username)
            print(new_password)

            # 예외처리 1 (ID or PASSWORD 가 완성되지 않음)
            if not new_username or not new_password:
                return render(request, 'join.html', {'error_msg' : '아이디 또는 비밀번호를 입력하세요.'})
            
            # 예외처리 2 (PASSWORD 확인에서의 error)
            elif new_password != new_password_check:
                return render(request, 'join.html', {'error_msg':'비밀번호가 서로 같지 않습니다.'})
            
            # 예외처리 3 (ID 중복)
            elif UserDB.objects.filter(username=new_username).exists():
                return render(request, 'join.html', {'error_msg':'이미 사용중인 아이디입니다.'})
            
            # no exception (이상 없음)
            new_users = UserDB.objects.create_user(username=new_username, password=new_password)
            new_users.save()
            
        
        # account == 'cancel'
        else:
            pass

        return redirect('users:LoginUrl')

    return render(request, 'join.html', {})

장고 ORM 을 통해서 새로운 사용자 DB를 생성할 때, create_user 을 사용하여 사용자를 저장하게 되면, 장고에서는 자동으로 PASSWORD 를 암호화 해서 저장해준다.

SQLite3 에서 보면, sha256 으로 암호화 되어있는걸 볼 수 있다.




틀린 부분이 있다면 지적 정말 감사합니다. 바로바로 수정하겠습니다.
profile
https://h0ng.dev

0개의 댓글