Communitygithub.com

django-security

Django 보안 모범 사례: 인증, 권한 부여, CSRF 보호, SQL 인젝션 방지, XSS 방지 및 안전한 배포 설정. Django 인증, 권한 부여, 입력 처리 또는 배포 설정을 검토할 때 사용하세요.

django-security란 무엇인가요?

이 스킬은 Django 애플리케이션의 보안 강화를 위한 포괄적인 가이드입니다. Django 개발자나 보안 감사자가 인증 및 권한 부여 설정, CSRF 보호, SQL 인젝션 방지, XSS(Cross-Site Scripting) 방지, 파일 업로드 보안, API 보안, 안전한 배포 구성 등 주요 취약점을 방어하는 방법을 다룹니다. 이 스킬의 핵심 가치는 실제 Django 프로젝트에 바로 적용할 수 있는 구체적인 코드 예제와 설정을 제공한다는 점입니다. 예를 들어, 프로덕션 설정에서 DEBUG=False 설정, 강력한 비밀번호 해싱 (Argon2), 커스텀 사용자 모델, ORM을 통한 SQL 인젝션 방지, 템플릿 이스케이핑을 통한 XSS 방지, CSRF 토큰 사용법 등을 상세히 설명합니다. 주요 지원 플랫폼은 Django 프레임워크이며, Django REST Framework(DRF)를 사용하는 API 보안 설정도 포함됩니다. 추가 라이브러리(예: python-magic, filetype, django-rest-framework-simplejwt)를 선택적으로 사용할 수 있습니다. 이 스킬은 Django 프로젝트의 보안 수준을 진단하고 개선하는 데 유용하며, 특히 프로덕션 환경 배포 전 필수 보안 설정을 점검할 때 활용할 수 있습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/django-security

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Django 보안 모범 사례

일반적인 취약점으로부터 Django 애플리케이션을 보호하기 위한 포괄적인 보안 지침입니다.

활성화 시기

  • Django 인증 및 권한 부여 설정 시
  • 사용자 권한 및 역할 구현 시
  • 프로덕션 보안 설정 구성 시
  • Django 애플리케이션의 보안 이슈 검토 시
  • Django 애플리케이션을 프로덕션에 배포 시

핵심 보안 설정

프로덕션 설정 구성

# settings/production.py
import os

DEBUG = False  # 중요: 프로덕션에서는 절대 True로 설정하지 마세요

ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')

# 보안 헤더
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000  # 1년
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'

# HTTPS 및 쿠키
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SAMESITE = 'Lax'

# 시크릿 키 (환경 변수로 설정해야 함)
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
    raise ImproperlyConfigured('DJANGO_SECRET_KEY 환경 변수가 필요합니다.')

# 비밀번호 검증
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 12,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

인증

사용자 정의 모델

# apps/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    """더 나은 보안을 위한 사용자 정의 모델입니다."""

    email = models.EmailField(unique=True)
    phone = models.CharField(max_length=20, blank=True)

    USERNAME_FIELD = 'email'  # 이메일을 사용자 이름으로 사용
    REQUIRED_FIELDS = ['username']

    class Meta:
        db_table = 'users'
        verbose_name = '사용자'
        verbose_name_plural = '사용자'

    def __str__(self):
        return self.email

# settings/base.py
AUTH_USER_MODEL = 'users.User'

비밀번호 해싱

# Django는 기본적으로 PBKDF2를 사용합니다. 더 강력한 보안을 위해:
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

세션 관리

# 세션 구성
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 또는 'db'
SESSION_CACHE_ALIAS = 'default'
SESSION_COOKIE_AGE = 3600 * 24 * 7  # 1주일
SESSION_SAVE_EVERY_REQUEST = False
SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # 더 나은 UX, 하지만 덜 안전함

권한 부여

권한

# models.py
from django.db import models
from django.contrib.auth.models import Permission

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        permissions = [
            ('can_publish', '게시물을 발행할 수 있음'),
            ('can_edit_others', '다른 사람의 게시물을 수정할 수 있음'),
        ]

    def user_can_edit(self, user):
        """사용자가 이 게시물을 편집할 수 있는지 확인합니다."""
        return self.author == user or user.has_perm('app.can_edit_others')

# views.py
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import UpdateView

class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
    model = Post
    permission_required = 'app.can_edit_others'
    raise_exception = True  # 리디렉션 대신 403 반환

    def get_queryset(self):
        """사용자가 자신의 게시물만 편집할 수 있도록 허용합니다."""
        return Post.objects.filter(author=self.request.user)

사용자 정의 권한

# permissions.py
from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):
    """소유자만 객체를 편집할 수 있도록 허용합니다."""

    def has_object_permission(self, request, view, obj):
        # 읽기 권한은 모든 요청에 허용
        if request.method in permissions.SAFE_METHODS:
            return True

        # 쓰기 권한은 소유자만
        return obj.author == request.user

class IsAdminOrReadOnly(permissions.BasePermission):
    """관리자는 모든 작업을 수행하고, 다른 사용자는 읽기 전용으로 허용합니다."""

    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True
        return request.user and request.user.is_staff

class IsVerifiedUser(permissions.BasePermission):
    """인증된 사용자만 허용합니다."""

    def has_permission(self, request, view):
        return request.user and request.user.is_authenticated and request.user.is_verified

역할 기반 접근 제어 (RBAC)

# models.py
from django.contrib.auth.models import AbstractUser, Group

class User(AbstractUser):
    ROLE_CHOICES = [
        ('admin', '관리자'),
        ('moderator', '중재자'),
        ('user', '일반 사용자'),
    ]
    role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')

    def is_admin(self):
        return self.role == 'admin' or self.is_superuser

    def is_moderator(self):
        return self.role in ['admin', 'moderator']

# Mixins
class AdminRequiredMixin:
    """관리자 역할을 요구하는 Mixin입니다."""

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated or not request.user.is_admin():
            from django.core.exceptions import PermissionDenied
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)

SQL 인젝션 방지

Django ORM 보호

# 좋음: Django ORM이 자동으로 매개변수를 이스케이프합니다.
def get_user(username):
    return User.objects.get(username=username)  # 안전

# 좋음: raw()와 함께 매개변수 사용
def search_users(query):
    return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])

# 나쁨: 사용자 입력을 직접 보간하지 마세요.
def get_user_bad(username):
    return User.objects.raw(f'SELECT * FROM users WHERE username = {username}')  # 취약!

# 좋음: 적절한 이스케이프와 함께 filter 사용
def get_users_by_email(email):
    return User.objects.filter(email__iexact=email)  # 안전

# 좋음: 복잡한 쿼리를 위해 Q 객체 사용
from django.db.models import Q
def search_users_complex(query):
    return User.objects.filter(
        Q(username__icontains=query) |
        Q(email__icontains=query)
    )  # 안전

raw() 추가 보안

# 원시 SQL을 사용해야 하는 경우 항상 매개변수를 사용하세요.
User.objects.raw(
    'SELECT * FROM users WHERE email = %s AND status = %s',
    [user_input_email, status]
)

XSS 방지

템플릿 이스케이핑

{# Django는 기본적으로 변수를 자동 이스케이프합니다 - 안전 #}
{{ user_input }}  {# 이스케이프된 HTML #}

{# 신뢰할 수 있는 콘텐츠에 대해서만 명시적으로 안전 표시 #}
{{ trusted_html|safe }}  {# 이스케이프되지 않음 #}

{# 안전한 HTML을 위해 템플릿 필터 사용 #}
{{ user_input|escape }}  {# 기본값과 동일 #}
{{ user_input|striptags }}  {# 모든 HTML 태그 제거 #}

{# 자바스크립트 이스케이핑 #}
<script>
    var username = {{ username|escapejs }};
</script>

안전한 문자열 처리

from django.utils.safestring import mark_safe
from django.utils.html import escape

# 나쁨: 사용자 입력을 이스케이프 없이 안전하게 표시하지 마세요.
def render_bad(user_input):
    return mark_safe(user_input)  # 취약!

# 좋음: 먼저 이스케이프한 다음 안전하게 표시
def render_good(user_input):
    return mark_safe(escape(user_input))

# 좋음: 변수가 있는 HTML을 위해 format_html 사용
from django.utils.html import format_html

def greet_user(username):
    return format_html('<span class="user">{}</span>', escape(username))

HTTP 헤더

# settings.py
SECURE_CONTENT_TYPE_NOSNIFF = True  # MIME 스니핑 방지
SECURE_BROWSER_XSS_FILTER = True  # XSS 필터 활성화
X_FRAME_OPTIONS = 'DENY'  # 클릭재킹 방지

# 사용자 정의 미들웨어
from django.conf import settings

class SecurityHeaderMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        response['X-Content-Type-Options'] = 'nosniff'
        response['X-Frame-Options'] = 'DENY'
        response['X-XSS-Protection'] = '1; mode=block'
        response['Content-Security-Policy'] = "default-src 'self'"
        return response

CSRF 보호

기본 CSRF 보호

# settings.py - CSRF는 기본적으로 활성화됩니다.
CSRF_COOKIE_SECURE = True  # HTTPS를 통해서만 전송
CSRF_COOKIE_HTTPONLY = True  # 자바스크립트 접근 방지
CSRF_COOKIE_SAMESITE = 'Lax'  # 일부 경우 CSRF 방지
CSRF_TRUSTED_ORIGINS = ['https://example.com']  # 신뢰할 수 있는 도메인

# 템플릿 사용법
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">제출</button>
</form>

# AJAX 요청
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

fetch('/api/endpoint/', {
    method: 'POST',
    headers: {
        'X-CSRFToken': getCookie('csrftoken'),
        'Content-Type': 'application/json',
    },
    body: JSON.stringify(data)
});

뷰 면제 (주의해서 사용)

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt  # 정말 필요한 경우에만 사용!
def webhook_view(request):
    # 외부 서비스의 웹훅
    pass

파일 업로드 보안

파일 검증

import os
import magic  # pip install python-magic
from django.core.exceptions import ValidationError

ALLOWED_MIMES = {
    'image/jpeg', 'image/png', 'image/gif', 'application/pdf',
}

MIME_TO_EXTENSIONS = {
    'image/jpeg': {'.jpg', '.jpeg'},
    'image/png': {'.png'},
    'image/gif': {'.gif'},
    'application/pdf': {'.pdf'},
}

def validate_file_type(value):
    """매직 바이트를 사용하여 파일 유형을 검증하고 확장자를 교차 확인합니다."""
    mime = magic.from_buffer(value.read(2048), mime=True)
    value.seek(0)

    if mime not in ALLOWED_MIMES:
        raise ValidationError('지원되지 않는 파일 유형입니다.')

    ext = os.path.splitext(value.name)[1].lower()
    if ext not in MIME_TO_EXTENSIONS.get(mime, set()):
        raise ValidationError('파일 확장자가 파일 내용과 일치하지 않습니다.')

def validate_file_size(value):
    """파일 크기 검증 (최대 5MB)."""
    if value.size > 5 * 1024 * 1024:
        raise ValidationError('파일이 너무 큽니다. 최대 크기는 5MB입니다.')

# models.py
class Document(models.Model):
    file = models.FileField(
        upload_to='documents/',
        validators=[validate_file_type, validate_file_size]
    )

libmagic 설치가 어려운 환경(예: 최소 컨테이너)의 경우, 대안으로 순수 Python filetype 패키지를 사용하세요:

import os
from django.core.exceptions import ValidationError

import filetype  # pip install filetype

ALLOWED_MIMES = {
    'image/jpeg', 'image/png', 'image/gif', 'application/pdf',
}

MIME_TO_EXTENSIONS = {
    'image/jpeg': {'.jpg', '.jpeg'},
    'image/png': {'.png'},
    'image/gif': {'.gif'},
    'application/pdf': {'.pdf'},
}

def validate_file_type(value):
    """매직 바이트를 사용하여 파일 유형을 검증합니다."""
    kind = filetype.guess(value.read(2048))
    value.seek(0)

    if kind is None or kind.mime not in ALLOWED_MIMES:
        raise ValidationError('지원되지 않는 파일 유형입니다.')

    ext = os.path.splitext(value.name)[1].lower()
    if ext not in MIME_TO_EXTENSIONS.get(kind.mime, set()):
        raise ValidationError('파일 확장자가 파일 내용과 일치하지 않습니다.')

안전한 파일 저장소

# settings.py
MEDIA_ROOT = '/var/www/media/'
MEDIA_URL = '/media/'

# 프로덕션에서는 미디어 파일을 위해 별도의 도메인 사용
MEDIA_DOMAIN = 'https://media.example.com'

# 사용자 업로드를 직접 제공하지 마세요.
# 정적 파일에는 whitenoise 또는 CDN 사용
# 미디어 파일에는 별도의 서버 또는 S3 사용

API 보안

속도 제한

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day',
        'upload': '10/hour',
    }
}

# 사용자 정의 스로틀
from rest_framework.throttling import UserRateThrottle

class BurstRateThrottle(UserRateThrottle):
    scope = 'burst'
    rate = '60/min'

class SustainedRateThrottle(UserRateThrottle):
    scope = 'sustained'
    rate = '1000/day'

API 인증

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

# views.py
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated

@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def api_endpoint(request):
    """인증된 사용자만 접근 가능한 API 엔드포인트입니다."""
    pass

배포 보안

환경 변수

# settings.py
import os
from pathlib import Path
from dotenv import load_dotenv  # pip install python-dotenv

load_dotenv()

# 필수 환경 변수
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
DATABASE_URL = os.environ['DATABASE_URL']

# 선택적 환경 변수
DEBUG = os.environ.get('DJANGO_DEBUG', 'False').lower() == 'true'
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost').split(',')

데이터베이스 보안

# settings.py
import dj_database_url  # pip install dj-database-url

# 프로덕션 데이터베이스 구성
database_url = os.environ['DATABASE_URL']
DATABASES = {
    'default': dj_database_url.parse(
        database_url,
        conn_max_age=600,
        ssl_require=True  # SSL 연결 강제
    )
}

HTTPS 강제

# settings.py
SECURE_SSL_REDIRECT = True  # HTTP를 HTTPS로 리디렉션
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')  # 프록시 뒤에서 사용
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

로깅 및 모니터링

# settings.py
import logging

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': '/var/log/django/security.log',
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'django.security': {
            'handlers': ['console', 'file'],
            'level': 'WARNING',
            'propagate': True,
        },
        'django.request': {
            'handlers': ['console', 'file'],
            'level': 'ERROR',
            'propagate': False,
        },
    },
}

배포 전 체크리스트

  • DEBUG = False 설정
  • SECRET_KEY가 환경 변수로 설정되고 길고 복잡한지 확인
  • ALLOWED_HOSTS가 올바르게 구성되었는지 확인
  • HTTPS를 통한 모든 트래픽 강제 (SSL 리디렉션)
  • 보안 쿠키 설정 (SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE)
  • HSTS 헤더 구성
  • 데이터베이스 연결에 SSL 사용
  • 비밀번호 해싱에 Argon2 사용
  • 파일 업로드 검증 구현
  • API에 속도 제한 설정
  • 안전하지 않은 패키지에 대한 종속성 검사 실행 (pip-audit)
  • Django check --deploy 명령 실행
# 배포 전 보안 검사 실행
python manage.py check --deploy

# 종속성 감사
pip install pip-audit
pip-audit

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

관련 스킬