내일배움캠프 본캠프 38일차 - Django 인증 시스템
1. 프로젝트 및 앱 설정
1.1. accounts 앱 생성
python manage.py startapp accounts
1.2. 프로젝트의 settings.py에 앱 등록
INSTALLED_APPS = [
...
'accounts',
]
1.3. 프로젝트의 urls.py에 accounts 앱 URL 포함
project/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('accounts.urls')),
]
2. 로그인 기능 구현하기
2.1. URL 설정
accounts/urls.py
from django.urls import path
from . import views
app_name = "accounts"
urlpatterns = [
path("login/", views.login, name="login"),
path("logout/", views.logout, name="logout"),
]
2.2. 로그인 뷰 구현
accounts/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.contrib.auth.forms import AuthenticationForm
def login(request):
if request.method == "POST":
form = AuthenticationForm(data=request.POST)
if form.is_valid():
auth_login(request, form.get_user()) # 로그인 처리
return redirect("home") # 로그인 성공 후 이동할 페이지 설정
else:
form = AuthenticationForm()
context = {"form": form}
return render(request, "accounts/login.html", context)
def logout(request):
if request.method == "POST":
auth_logout(request)
return redirect("home")
2.3. 로그인 템플릿 구현
accounts/templates/accounts/login.html
{% extends "base.html" %}
{% block content %}
<h1>로그인</h1>
<form action="{% url 'accounts:login' %}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">로그인</button>
</form>
{% endblock content %}
2.4. 로그아웃 템플릿 적용
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Django Auth</title>
</head>
<body>
<div class="navbar">
{% if request.user.is_authenticated %}
<h3>안녕하세요, {{ user.username }}님</h3>
<form action="{% url 'accounts:logout' %}" method="POST">
{% csrf_token %}
<input type="submit" value="로그아웃">
</form>
{% else %}
<a href="{% url 'accounts:login' %}">로그인</a>
{% endif %}
</div>
<div class="container">
{% block content %}
{% endblock content %}
</div>
</body>
</html>
3. 관리자 계정 생성 및 테스트
3.1. 슈퍼유저 생성
python manage.py createsuperuser
- username: admin
- email: admin@example.com
- password: ********
이제 관리자 계정으로 로그인할 수 있습니다.
3.2. 개발 서버 실행 및 테스트
python manage.py runserver
- 브라우저에서 http://127.0.0.1:8000/accounts/login/로 이동.
- 사용자명과 비밀번호 입력 후 로그인.
4. 로그인 접근 제어 (is_authenticated)
Django는 request.user.is_authenticated 속성을 제공하여 로그인 여부를 확인할 수 있습니다.
4.1. 로그인 여부 체크
{% if request.user.is_authenticated %}
<p>안녕하세요, {{ user.username }}님!</p>
<form action="{% url 'accounts:logout' %}" method="POST">
{% csrf_token %}
<input type="submit" value="로그아웃">
</form>
{% else %}
<a href="{% url 'accounts:login' %}">로그인</a>
{% endif %}
5. 인증이 필요한 페이지 보호하기
Django의 @login_required 데코레이터를 사용하여 로그인해야만 접근 가능한 페이지를 설정할 수 있습니다.
5.1. 인증이 필요한 뷰 설정
accounts/views.py
from django.contrib.auth.decorators import login_required
@login_required
def profile(request):
return render(request, "accounts/profile.html")
5.2. 로그인 페이지로 리디렉션 설정
settings.py
LOGIN_URL = 'accounts:login' # 로그인되지 않은 사용자가 접근 시 이동할 경로
6. 회원 탈퇴 및 계정 삭제 기능 구현
6.1. 뷰에 삭제 기능 추가
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
from django.shortcuts import get_object_or_404, redirect
from django.contrib.auth.models import User
@require_POST
@login_required
def delete_user(request):
user = get_object_or_404(User, pk=request.user.pk)
user.delete()
return redirect("home")
6.2. 템플릿에 탈퇴 버튼 추가
<form action="{% url 'accounts:delete_user' %}" method="POST">
{% csrf_token %}
<input type="submit" value="계정 삭제">
</form>
7. 로그인 및 로그아웃 기능 테스트
- http://127.0.0.1:8000/accounts/login/로 이동하여 로그인 시도.
- 로그인 후 base.html의 네비게이션 바에서 사용자 정보 확인.
- 로그아웃 버튼 클릭 후 정상적으로 로그아웃되는지 확인.
8. Django 인증 시스템의 장점
- 세션 관리 자동화: Django가 세션을 자동으로 처리.
- 보안 기능 내장: 비밀번호 해싱 및 로그인 시도 제한.
- 확장성: 커스텀 사용자 모델을 쉽게 구현 가능.
- 인증 데코레이터 제공: @login_required 등으로 인증이 필요한 페이지 보호 가능.
9. 인증 관련 주요 함수 정리
함수 | 설명 |
login(request, user) | 사용자를 로그인 처리 후 세션에 저장 |
logout(request) | 현재 로그인된 사용자 로그아웃 |
authenticate() | 사용자 인증 (ID, 비밀번호 확인) |
is_authenticated | 사용자가 로그인 상태인지 확인 |
Django의 인증(Authentication) 시스템을 사용하여 로그인, 로그아웃, 접근 제한 기능을 구현하는 방법을 살펴보겠습니다.
1. superuser(슈퍼유저) 생성
Django의 관리자 페이지(admin)에 접근할 수 있는 최고 권한의 사용자입니다.
1.1. superuser 생성하기
python manage.py createsuperuser
입력해야 할 정보:
- username: admin
- email: admin@example.com
- password: admin1234
superuser 권한 구분
- User: 일반 사용자.
- Staff: 관리자 페이지 접근 가능.
- Superuser: 모든 권한 보유.
2. 로그인 및 로그아웃 구현하기
2.1. URL 설정
accounts/urls.py
from django.urls import path
from . import views
app_name = "accounts"
urlpatterns = [
path("login/", views.login, name="login"),
path("logout/", views.logout, name="logout"),
]
2.2. 로그인 뷰 작성
accounts/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login as auth_login
from django.contrib.auth.forms import AuthenticationForm
def login(request):
if request.method == "POST":
form = AuthenticationForm(data=request.POST)
if form.is_valid():
auth_login(request, form.get_user())
return redirect("home") # 로그인 후 이동할 페이지
else:
form = AuthenticationForm()
context = {"form": form}
return render(request, "accounts/login.html", context)
2.3. 로그인 템플릿
accounts/templates/accounts/login.html
{% extends "base.html" %}
{% block content %}
<h1>로그인</h1>
<form action="{% url 'accounts:login' %}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">로그인</button>
</form>
{% endblock content %}
2.4. 로그아웃 뷰 작성
accounts/views.py
from django.contrib.auth import logout as auth_logout
from django.views.decorators.http import require_POST
@require_POST
def logout(request):
if request.user.is_authenticated:
auth_logout(request)
return redirect("home")
2.5. 로그아웃 링크 추가 (템플릿)
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Django Auth</title>
</head>
<body>
<div class="navbar">
{% if request.user.is_authenticated %}
<h3>안녕하세요, {{ user.username }}님</h3>
<form action="{% url 'accounts:logout' %}" method="POST">
{% csrf_token %}
<input type="submit" value="로그아웃">
</form>
{% else %}
<a href="{% url 'accounts:login' %}">로그인</a>
{% endif %}
</div>
<div class="container">
{% block content %}
{% endblock content %}
</div>
</body>
</html>
3. 접근 제한 (로그인 여부 확인)
Django는 다음 두 가지 방법으로 로그인한 사용자만 특정 페이지에 접근할 수 있도록 제한합니다.
3.1. is_authenticated 속성 사용
템플릿에서 현재 사용자의 로그인 여부를 확인할 수 있습니다.
{% if request.user.is_authenticated %}
<p>환영합니다, {{ user.username }}님!</p>
<form action="{% url 'accounts:logout' %}" method="POST">
{% csrf_token %}
<input type="submit" value="로그아웃">
</form>
{% else %}
<a href="{% url 'accounts:login' %}">로그인</a>
{% endif %}
3.2. @login_required 데코레이터 사용
Django의 @login_required 데코레이터를 사용하면, 특정 뷰가 로그인된 사용자만 접근할 수 있도록 보호할 수 있습니다.
accounts/views.py
from django.contrib.auth.decorators import login_required
@login_required
def profile(request):
return render(request, "accounts/profile.html")
settings.py에서 리디렉션 설정
LOGIN_URL = 'accounts:login'
사용자가 로그인이 되어있지 않은 상태에서 profile 페이지를 접근하려 하면, 자동으로 로그인 페이지로 리디렉션됩니다.
4. HTTP 요청 처리 방법
Django는 HTTP 요청을 처리할 때 다음과 같은 방식을 제공합니다.
함수 | 설명 |
render() | 템플릿을 렌더링하여 사용자에게 반환 |
redirect() | 특정 URL로 이동 |
get_object_or_404() | 객체가 없을 경우 404 에러 페이지 반환 |
get_list_or_404() | 리스트가 없을 경우 404 에러 페이지 반환 |
예제:
from django.shortcuts import render, get_object_or_404
def article_detail(request, pk):
article = get_object_or_404(Article, pk=pk)
return render(request, "articles/detail.html", {"article": article})
5. HTTP 요청을 제한하는 데코레이터
Django는 특정 HTTP 메서드(POST, GET 등)만 허용할 수 있도록 여러 데코레이터를 제공합니다.
데코레이터 | 설명 |
@require_http_methods | 특정 HTTP 메서드만 허용 |
@require_POST | POST 요청만 허용 |
@require_GET | GET 요청만 허용 |
예제:
from django.views.decorators.http import require_POST
@require_POST
def delete_article(request, pk):
if request.user.is_authenticated:
article = get_object_or_404(Article, pk=pk)
article.delete()
return redirect("articles:list")
6. 회원가입 기능 추가 (다음 단계)
현재는 superuser를 통해 관리자 계정을 만들었지만, 사용자 회원가입을 구현할 수도 있습니다. Django는 UserCreationForm을 제공하여 손쉽게 회원가입 기능을 구현할 수 있습니다.
7. 인증 시스템 요약 정리
기능 | 설명 |
로그인 | 사용자가 로그인하면 세션에 사용자 정보를 저장 |
로그아웃 | 세션을 제거하고, 클라이언트 쿠키도 삭제 |
접근 제한 | 로그인하지 않은 사용자의 특정 페이지 접근 제한 |
superuser 생성 | 관리자 페이지 접근 및 모든 권한 부여 |
템플릿에서 사용자 확인 | request.user.is_authenticated 속성으로 상태 확인 |
Django의 login_required 데코레이터 및 비로그인 상태 처리 방법
Django의 @login_required 데코레이터는 특정 뷰에 대해 로그인한 사용자만 접근할 수 있도록 제한하는 기능을 제공합니다.
비로그인 사용자가 접근하려고 할 경우 설정된 로그인 URL로 리다이렉트되며, 로그인 후에는 원래 접근하려 했던 페이지로 다시 이동할 수 있도록 처리됩니다.
1. @login_required의 기본 동작
- 사용자가 보호된 페이지(예: /articles/create/)에 접근 시도.
- 로그인되지 않은 경우, **기본 로그인 URL(settings.LOGIN_URL)**로 리다이렉트.
- 기본 설정: /accounts/login/
- 리다이렉트 시 next 쿼리스트링이 포함됨 (/accounts/login/?next=/articles/create/)
- 로그인 후, next URL로 자동 이동.
- 로그인된 경우, 뷰의 로직이 정상 실행.
설정 예시 (settings.py):
LOGIN_URL = '/accounts/login/'
2. login_required 적용 예제
로그인 여부가 필요한 update, delete 뷰에 적용합니다.
articles/views.py
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods, require_POST
from django.shortcuts import render, redirect, get_object_or_404
from .models import Article
from .forms import ArticleForm
@login_required
@require_POST
def delete(request, pk):
article = get_object_or_404(Article, pk=pk)
article.delete()
return redirect("articles:articles")
@login_required
@require_http_methods(["GET", "POST"])
def update(request, pk):
article = get_object_or_404(Article, pk=pk)
if request.method == "POST":
form = ArticleForm(request.POST, instance=article)
if form.is_valid():
article = form.save()
return redirect("articles:article_detail", article.pk)
else:
form = ArticleForm(instance=article)
context = {
"form": form,
"article": article,
}
return render(request, "articles/update.html", context)
3. @login_required 적용 시 발생 가능한 문제점
비로그인 상태에서 POST 요청이 필요한 뷰(예: delete)에 접근하면, 로그인 후 GET 요청으로 리디렉션되며 405 Method Not Allowed 오류가 발생할 수 있습니다.
문제 발생 흐름:
- 비로그인 상태에서 삭제 요청 (/articles/delete/1) 클릭.
- /accounts/login/?next=/articles/delete/1/ 로 리다이렉트됨.
- 로그인 성공 후 /articles/delete/1/로 이동되지만 GET 요청으로 처리됨.
- @require_POST 데코레이터 때문에 오류 발생.
해결 방법:
- login_required를 제거하고, 내부에서 인증 여부를 체크하도록 수정.
3.1. 수정된 delete 뷰 예제 (내부 인증 체크 적용)
from django.views.decorators.http import require_POST
from django.shortcuts import get_object_or_404, redirect
from .models import Article
@require_POST
def delete(request, pk):
if request.user.is_authenticated:
article = get_object_or_404(Article, pk=pk)
article.delete()
return redirect("articles:articles")
설명:
- 비로그인 상태에서 접근 시 리다이렉트는 발생하지 않고, 삭제 로직이 실행되지 않음.
- 직접 접근 시에는 405 Method Not Allowed 오류가 발생하도록 유지.
4. 로그인 후 특정 페이지로 리디렉트 처리
@login_required 데코레이터를 사용하면 Django는 기본적으로 사용자가 로그인 성공 후 원래 요청한 페이지로 이동할 수 있도록 지원합니다.
템플릿에서 next 값을 활용한 처리 예제:
<form action="{% url 'accounts:login' %}" method="POST">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.GET.next }}">
{{ form.as_p }}
<button type="submit">로그인</button>
</form>
뷰에서 처리 예제:
def login_view(request):
if request.method == "POST":
form = AuthenticationForm(data=request.POST)
if form.is_valid():
auth_login(request, form.get_user())
next_url = request.GET.get('next') or 'articles:index'
return redirect(next_url)
else:
form = AuthenticationForm()
return render(request, "accounts/login.html", {"form": form})
5. 다양한 HTTP 메서드를 처리하는 데코레이터
Django에서는 특정 HTTP 메서드만 처리할 수 있도록 다양한 데코레이터를 제공합니다.
데코레이터 | 설명 |
@require_http_methods(["GET", "POST"]) | 특정 HTTP 메서드만 허용. |
@require_POST | POST 요청만 허용. |
@require_GET | GET 요청만 허용. |
예제:
from django.views.decorators.http import require_http_methods, require_POST
@require_POST
def delete_article(request, pk):
article = get_object_or_404(Article, pk=pk)
article.delete()
return redirect("articles:articles")
6. Django login_required 데코레이터 요약
기능 | 설명 |
보호된 뷰 접근 | 로그인한 사용자만 접근 가능. |
리다이렉트 경로 설정 | settings.LOGIN_URL에 지정된 경로로 이동. |
next 파라미터 활용 | 로그인 후 이전 페이지로 리디렉트 지원. |
기본 HTTP 메서드 처리 문제 | @require_POST와 함께 사용 시 주의 필요. |
7. 추가 보안 조치
- 세션 설정 강화 (settings.py)
SESSION_COOKIE_AGE = 3600 # 1시간 동안 유지
SESSION_COOKIE_HTTPONLY = True # JS 접근 방지
SESSION_COOKIE_SECURE = True # HTTPS 전용
SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 브라우저 종료 시 세션 삭제
- 보안 기능 적용
- @login_required와 함께 @require_POST 조합을 통해 불필요한 접근 방지.
- CSRF 토큰을 반드시 템플릿에 포함.
8. 테스트 시나리오
- 비로그인 상태에서 /articles/create/로 접근 시 /accounts/login/?next=/articles/create/로 이동되는지 확인.
- 로그인 후 next 값이 유지되어 /articles/create/ 페이지로 정상 리디렉트되는지 확인.
- 로그인 없이 직접 /articles/delete/1/로 접근 시 405 오류 발생 확인.
- @require_POST 데코레이터 적용 여부 확인.