TIL

내일배움캠프 본캠프 38일차 - Django 인증 시스템

수현조 2025. 1. 17. 22:44

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

이제 관리자 계정으로 로그인할 수 있습니다.


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. 로그인 및 로그아웃 기능 테스트

  1. http://127.0.0.1:8000/accounts/login/로 이동하여 로그인 시도.
  2. 로그인 후 base.html의 네비게이션 바에서 사용자 정보 확인.
  3. 로그아웃 버튼 클릭 후 정상적으로 로그아웃되는지 확인.

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의 기본 동작

  1. 사용자가 보호된 페이지(예: /articles/create/)에 접근 시도.
  2. 로그인되지 않은 경우, **기본 로그인 URL(settings.LOGIN_URL)**로 리다이렉트.
    • 기본 설정: /accounts/login/
    • 리다이렉트 시 next 쿼리스트링이 포함됨 (/accounts/login/?next=/articles/create/)
  3. 로그인 후, next URL로 자동 이동.
  4. 로그인된 경우, 뷰의 로직이 정상 실행.

설정 예시 (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 오류가 발생할 수 있습니다.

문제 발생 흐름:

  1. 비로그인 상태에서 삭제 요청 (/articles/delete/1) 클릭.
  2. /accounts/login/?next=/articles/delete/1/ 로 리다이렉트됨.
  3. 로그인 성공 후 /articles/delete/1/로 이동되지만 GET 요청으로 처리됨.
  4. @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. 추가 보안 조치

  1. 세션 설정 강화 (settings.py)
SESSION_COOKIE_AGE = 3600  # 1시간 동안 유지
SESSION_COOKIE_HTTPONLY = True  # JS 접근 방지
SESSION_COOKIE_SECURE = True  # HTTPS 전용
SESSION_EXPIRE_AT_BROWSER_CLOSE = True  # 브라우저 종료 시 세션 삭제
  1. 보안 기능 적용
    • @login_required와 함께 @require_POST 조합을 통해 불필요한 접근 방지.
    • CSRF 토큰을 반드시 템플릿에 포함.

8. 테스트 시나리오

  1. 비로그인 상태에서 /articles/create/로 접근 시 /accounts/login/?next=/articles/create/로 이동되는지 확인.
  2. 로그인 후 next 값이 유지되어 /articles/create/ 페이지로 정상 리디렉트되는지 확인.
  3. 로그인 없이 직접 /articles/delete/1/로 접근 시 405 오류 발생 확인.
  4. @require_POST 데코레이터 적용 여부 확인.