troubleshooting2025-03-30·7 min·131/348

Django Middleware 순서 문제와 해결 방법

Django middleware의 실행 순서를 이해하고, 순서로 인한 버그를 진단하고 해결하는 방법을 알아봅니다.

Django Middleware 순서 문제와 해결 방법

Django의 middleware는 request/response 처리 과정에서 중요한 역할을 합니다. 하지만 middleware의 실행 순서를 잘못 이해하면 예상치 못한 버그가 발생할 수 있습니다.

Environment

$ python --version
Python 3.11.5

$ pip show django
Name: Django
Version: 4.2.5

$ cat /etc/os-release | head -3
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"

Problem: Custom Middleware가 인증되지 않은 요청을 통과시킴

다음과 같은 상황을 가정해봅시다:

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'myapp.middleware.AuthenticationMiddleware',  # Custom middleware
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
]
# myapp/middleware.py
class AuthenticationMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        # Check if user is authenticated
        if not hasattr(request, 'user') or request.user.is_anonymous:
            # Redirect to login
            from django.http import HttpResponseRedirect
            from django.urls import reverse
            return HttpResponseRedirect(reverse('login'))
        
        response = self.get_response(request)
        return response

실행 결과:

$ python manage.py runserver
$ curl -v http://localhost:8000/dashboard/
> GET /dashboard/ HTTP/1.1
> Host: localhost:8000

< HTTP/1.1 302 Found
< Location: /accounts/login/

인증된 사용자도 로그인 페이지로 리다이렉트되는 문제가 발생합니다.

Analysis: Middleware 실행 순서 이해

Django middleware는 request와 response 두 가지 시점에서 실행됩니다:

# middleware execution order visualization
"""
Request phase (top to bottom):
1. SecurityMiddleware.__call__ → get_response(request)
2. SessionMiddleware.__call__ → get_response(request)
3. AuthenticationMiddleware.__call__ → get_response(request)
   ↓
   View executes here
   ↓
Response phase (bottom to top):
3. AuthenticationMiddleware processes response
2. SessionMiddleware processes response
1. SecurityMiddleware processes response
"""

문제를 진단하기 위해 logging을 추가합니다:

# myapp/middleware.py
import logging

logger = logging.getLogger(__name__)

class AuthenticationMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        logger.debug(f"Auth middleware: Processing request to {request.path}")
        logger.debug(f"Auth middleware: Has user attr: {hasattr(request, 'user')}")
        
        if hasattr(request, 'user'):
            logger.debug(f"Auth middleware: User: {request.user}, Is anonymous: {request.user.is_anonymous}")
        
        response = self.get_response(request)
        
        logger.debug(f"Auth middleware: Response status: {response.status_code}")
        return response

로그 출력:

DEBUG:myapp.middleware:Auth middleware: Processing request to /dashboard/
DEBUG:myapp.middleware:Auth middleware: Has user attr: False

request.user 속성이 설정되기 전에 custom middleware가 실행되고 있음을 확인할 수 있습니다.

Solution: Middleware 순서 수정 및 개선

방법 1: Middleware 순서 올바르게 설정

# settings.py - 올바른 순서
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',  # 먼저 실행
    'django.contrib.messages.middleware.MessageMiddleware',
    'myapp.middleware.CustomAuthenticationMiddleware',  # 그 다음 실행
]

방법 2: AuthenticationMiddleware 상속

# myapp/middleware.py
from django.contrib.auth.middleware import AuthenticationMiddleware as DjangoAuthMiddleware

class CustomAuthenticationMiddleware(DjangoAuthMiddleware):
    def __call__(self, request):
        # Call parent first to set request.user
        response = super().__call__(request)
        
        # Now check authentication
        if request.user.is_anonymous:
            from django.http import HttpResponseRedirect
            from django.urls import reverse
            return HttpResponseRedirect(reverse('login'))
        
        return response

방법 3: process_view 사용

# myapp/middleware.py
class AuthenticationMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        response = self.get_response(request)
        return response
    
    def process_view(self, request, view_func, view_args, view_kwargs):
        # This runs after URL resolution but before view execution
        # request.user is available here
        if request.user.is_anonymous:
            from django.http import HttpResponseRedirect
            from django.urls import reverse
            return HttpResponseRedirect(reverse('login'))
        return None  # Continue to view

Advanced: Conditional Middleware Application

# middleware.py
class ConditionalMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        # Skip certain paths
        skip_paths = ['/api/health', '/static/', '/admin/']
        
        if any(request.path.startswith(path) for path in skip_paths):
            return self.get_response(request)
        
        # Process request...
        response = self.get_response(request)
        return response

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'myapp.middleware.ConditionalMiddleware',  # After auth middleware
]

Lessons Learned

  1. Middleware 순서가 중요합니다: Django는 MIDDLEWARE 리스트의 순서대로 middleware를 실행합니다. 인증 관련 middleware는 항상 Django의 AuthenticationMiddleware 다음에 와야 합니다.

  2. Request/response 두 단계: 각 middleware는 request 처리와 response 처리 두 단계를 거칩니다. 순서를 혼동하면 버그가 발생합니다.

  3. process_view 활용: view 실행 전에 request.user를 확인해야 한다면 process_view 메서드를 사용하는 것이 더 안전합니다.

  4. 로깅으로 디버깅: middleware 순서 문제는 로깅을 통해 쉽게 진단할 수 있습니다.

  5. Third-party middleware 주의: Django REST Framework나 CORS 관련 middleware도 순서에 민감합니다.


This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.