게시글 전체 소스코드는 GitHub 에서 다운로드 가능합니다.
장고에서는 기본적으로 사용자 인증(authentication system) 을 위한 로그인과 로그아웃, 회원가입 을 위한 기능들을 제공해준다.
인증(authentication)을 위한 모듈
- authenticate : 클라이언트로 부터 입력받은 ID 와 PASSWORD 를 User 데이터베이스 내에서 해당 객체가 있는지 검사하고, 있다면 객체를 반환.
인가(authorization)를 위한 모듈
- login : authenticate 함수를 통해서 DB 내에서 반환받은 객체를 현재 session 데이터 내에 로그인 데이터로 저장.
- logout : session cookie 에서 클라이언트의 로그인 정보를 삭제.
# tree
mysite/
settings.py/
urls.py/
users/
templates/
login.html/
join.html/
urls.py/
views.py/
models.py/
로그인 페이지를 만들기에 앞서, 제일 먼저 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 이 사라지고 로그아웃 버튼이 보인다.
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 하면 구현이 끝난 셈이다.
새로운 사용자를 등록하기 위해서 회원가입 페이지에서는 몇가지 고려사항이 있다.
- 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>
# 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 으로 암호화 되어있는걸 볼 수 있다.